Compare commits
7 Commits
traits
...
android12_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca3d807e7f | ||
|
|
dce0867737 | ||
|
|
5c4c28c973 | ||
|
|
98628f25b5 | ||
|
|
e1dbe4f71c | ||
|
|
b1734ab737 | ||
|
|
44aa7f2c88 |
12
README.md
12
README.md
@@ -491,6 +491,18 @@ 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
|
||||||
|
|||||||
@@ -114,10 +114,23 @@ conf.set('PORTABLE', get_option('portable'))
|
|||||||
conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183')
|
conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183')
|
||||||
conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
|
conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
|
||||||
|
|
||||||
|
# the default max video size for both dimensions, in pixels
|
||||||
|
# overridden by option --max-size
|
||||||
|
conf.set('DEFAULT_MAX_SIZE', '0') # 0: unlimited
|
||||||
|
|
||||||
|
# the default video orientation
|
||||||
|
# natural device orientation is 0 and each increment adds 90 degrees
|
||||||
|
# counterclockwise
|
||||||
|
# overridden by option --lock-video-orientation
|
||||||
|
conf.set('DEFAULT_LOCK_VIDEO_ORIENTATION', '-1') # -1: unlocked
|
||||||
|
|
||||||
# the default video bitrate, in bits/second
|
# the default video bitrate, in bits/second
|
||||||
# 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'))
|
||||||
|
|
||||||
|
|||||||
@@ -155,6 +155,10 @@ 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.
|
||||||
|
|||||||
@@ -10,9 +10,6 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str_util.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
#define STR_IMPL_(x) #x
|
|
||||||
#define STR(x) STR_IMPL_(x)
|
|
||||||
|
|
||||||
void
|
void
|
||||||
scrcpy_print_usage(const char *arg0) {
|
scrcpy_print_usage(const char *arg0) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
@@ -26,7 +23,7 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" -b, --bit-rate value\n"
|
" -b, --bit-rate value\n"
|
||||||
" Encode the video at the given bit-rate, expressed in bits/s.\n"
|
" Encode the video at the given bit-rate, expressed in bits/s.\n"
|
||||||
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
|
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
|
||||||
" Default is " STR(DEFAULT_BIT_RATE) ".\n"
|
" Default is %d.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --codec-options key[:type]=value[,...]\n"
|
" --codec-options key[:type]=value[,...]\n"
|
||||||
" Set a list of comma-separated key:type=value options for the\n"
|
" Set a list of comma-separated key:type=value options for the\n"
|
||||||
@@ -84,7 +81,7 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" Possible values are -1 (unlocked), 0, 1, 2 and 3.\n"
|
" Possible values are -1 (unlocked), 0, 1, 2 and 3.\n"
|
||||||
" Natural device orientation is 0, and each increment adds a\n"
|
" Natural device orientation is 0, and each increment adds a\n"
|
||||||
" 90 degrees rotation counterclockwise.\n"
|
" 90 degrees rotation counterclockwise.\n"
|
||||||
" Default is -1 (unlocked).\n"
|
" Default is %d%s.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --max-fps value\n"
|
" --max-fps value\n"
|
||||||
" Limit the frame rate of screen capture (officially supported\n"
|
" Limit the frame rate of screen capture (officially supported\n"
|
||||||
@@ -94,7 +91,7 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" Limit both the width and height of the video to value. The\n"
|
" Limit both the width and height of the video to value. The\n"
|
||||||
" other dimension is computed so that the device aspect-ratio\n"
|
" other dimension is computed so that the device aspect-ratio\n"
|
||||||
" is preserved.\n"
|
" is preserved.\n"
|
||||||
" Default is 0 (unlimited).\n"
|
" Default is %d%s.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -n, --no-control\n"
|
" -n, --no-control\n"
|
||||||
" Disable device control (mirror the device in read-only).\n"
|
" Disable device control (mirror the device in read-only).\n"
|
||||||
@@ -113,8 +110,7 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
"\n"
|
"\n"
|
||||||
" -p, --port port[:port]\n"
|
" -p, --port port[:port]\n"
|
||||||
" Set the TCP port (range) used by the client to listen.\n"
|
" Set the TCP port (range) used by the client to listen.\n"
|
||||||
" Default is " STR(DEFAULT_LOCAL_PORT_RANGE_FIRST) ":"
|
" Default is %d:%d.\n"
|
||||||
STR(DEFAULT_LOCAL_PORT_RANGE_LAST) ".\n"
|
|
||||||
"\n"
|
"\n"
|
||||||
" --prefer-text\n"
|
" --prefer-text\n"
|
||||||
" Inject alpha characters and space as text events instead of\n"
|
" Inject alpha characters and space as text events instead of\n"
|
||||||
@@ -143,6 +139,12 @@ 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"
|
||||||
@@ -295,7 +297,12 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
"\n"
|
"\n"
|
||||||
" Drag & drop APK file\n"
|
" Drag & drop APK file\n"
|
||||||
" Install APK from computer\n"
|
" Install APK from computer\n"
|
||||||
"\n", arg0);
|
"\n",
|
||||||
|
arg0,
|
||||||
|
DEFAULT_BIT_RATE,
|
||||||
|
DEFAULT_LOCK_VIDEO_ORIENTATION, DEFAULT_LOCK_VIDEO_ORIENTATION >= 0 ? "" : " (unlocked)",
|
||||||
|
DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
|
||||||
|
DEFAULT_LOCAL_PORT_RANGE_FIRST, DEFAULT_LOCAL_PORT_RANGE_LAST);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@@ -660,7 +667,6 @@ 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[]) {
|
||||||
@@ -711,8 +717,6 @@ 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 },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -810,8 +814,7 @@ 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:
|
||||||
LOGW("Option --render-expired-frames has been removed. This "
|
opts->render_expired_frames = true;
|
||||||
"flag has been ignored.");
|
|
||||||
break;
|
break;
|
||||||
case OPT_WINDOW_TITLE:
|
case OPT_WINDOW_TITLE:
|
||||||
opts->window_title = optarg;
|
opts->window_title = optarg;
|
||||||
@@ -882,9 +885,6 @@ 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;
|
||||||
|
|||||||
@@ -8,7 +8,4 @@
|
|||||||
#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
|
||||||
|
|||||||
@@ -8,9 +8,20 @@
|
|||||||
# 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(),
|
||||||
@@ -22,6 +33,15 @@
|
|||||||
# 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
|
||||||
|
|||||||
@@ -1,43 +1,38 @@
|
|||||||
#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 "trait/frame_sink.h"
|
#include "util/buffer_util.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
/** Downcast packet_sink to decoder */
|
// set the decoded frame as ready for rendering, and notify
|
||||||
#define DOWNCAST(SINK) container_of(SINK, struct decoder, packet_sink)
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
decoder_close_first_sinks(struct decoder *decoder, unsigned count) {
|
push_frame(struct decoder *decoder) {
|
||||||
while (count) {
|
bool previous_frame_skipped;
|
||||||
struct sc_frame_sink *sink = decoder->sinks[--count];
|
video_buffer_offer_decoded_frame(decoder->video_buffer,
|
||||||
sink->ops->close(sink);
|
&previous_frame_skipped);
|
||||||
|
if (previous_frame_skipped) {
|
||||||
|
// the previous EVENT_NEW_FRAME will consume this frame
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
static SDL_Event new_frame_event = {
|
||||||
|
.type = EVENT_NEW_FRAME,
|
||||||
|
};
|
||||||
|
SDL_PushEvent(&new_frame_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
void
|
||||||
decoder_close_sinks(struct decoder *decoder) {
|
decoder_init(struct decoder *decoder, struct video_buffer *vb) {
|
||||||
decoder_close_first_sinks(decoder, decoder->sink_count);
|
decoder->video_buffer = vb;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
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) {
|
||||||
@@ -51,105 +46,52 @@ decoder_open(struct decoder *decoder, const AVCodec *codec) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!decoder_open_sinks(decoder)) {
|
|
||||||
LOGE("Could not open decoder sinks");
|
|
||||||
av_frame_free(&decoder->frame);
|
|
||||||
avcodec_close(decoder->codec_ctx);
|
|
||||||
avcodec_free_context(&decoder->codec_ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
void
|
||||||
decoder_close(struct decoder *decoder) {
|
decoder_close(struct decoder *decoder) {
|
||||||
decoder_close_sinks(decoder);
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
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) {
|
||||||
bool is_config = packet->pts == AV_NOPTS_VALUE;
|
// the new decoding/encoding API has been introduced by:
|
||||||
if (is_config) {
|
// <http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=7fc329e2dd6226dfecaa4a1d7adf353bf2773726>
|
||||||
// nothing to do
|
#ifdef SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API
|
||||||
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, decoder->frame);
|
ret = avcodec_receive_frame(decoder->codec_ctx,
|
||||||
|
decoder->video_buffer->decoding_frame);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
// a frame was received
|
// a frame was received
|
||||||
bool ok = push_frame_to_sinks(decoder, decoder->frame);
|
push_frame(decoder);
|
||||||
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
decoder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
|
|
||||||
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
|
void
|
||||||
decoder_init(struct decoder *decoder) {
|
decoder_interrupt(struct decoder *decoder) {
|
||||||
static const struct sc_packet_sink_ops ops = {
|
video_buffer_interrupt(decoder->video_buffer);
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,27 +3,29 @@
|
|||||||
|
|
||||||
#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>
|
||||||
|
|
||||||
#define DECODER_MAX_SINKS 1
|
struct video_buffer;
|
||||||
|
|
||||||
struct decoder {
|
struct decoder {
|
||||||
struct sc_packet_sink packet_sink; // packet sink trait
|
struct video_buffer *video_buffer;
|
||||||
|
|
||||||
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);
|
decoder_init(struct decoder *decoder, struct video_buffer *vb);
|
||||||
|
|
||||||
|
bool
|
||||||
|
decoder_open(struct decoder *decoder, const AVCodec *codec);
|
||||||
|
|
||||||
void
|
void
|
||||||
decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink);
|
decoder_close(struct decoder *decoder);
|
||||||
|
|
||||||
|
bool
|
||||||
|
decoder_push(struct decoder *decoder, const AVPacket *packet);
|
||||||
|
|
||||||
|
void
|
||||||
|
decoder_interrupt(struct decoder *decoder);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -480,7 +480,9 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
return;
|
return;
|
||||||
case SDLK_i:
|
case SDLK_i:
|
||||||
if (!shift && !repeat && down) {
|
if (!shift && !repeat && down) {
|
||||||
switch_fps_counter_state(im->fps_counter);
|
struct fps_counter *fps_counter =
|
||||||
|
im->video_buffer->fps_counter;
|
||||||
|
switch_fps_counter_state(fps_counter);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_n:
|
case SDLK_n:
|
||||||
|
|||||||
@@ -11,10 +11,11 @@
|
|||||||
#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 fps_counter *fps_counter;
|
struct video_buffer *video_buffer;
|
||||||
struct screen *screen;
|
struct screen *screen;
|
||||||
|
|
||||||
// SDL reports repeated events as a boolean, but Android expects the actual
|
// SDL reports repeated events as a boolean, but Android expects the actual
|
||||||
|
|||||||
@@ -5,9 +5,6 @@
|
|||||||
|
|
||||||
#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 *
|
||||||
@@ -60,6 +57,50 @@ 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) {
|
||||||
@@ -69,6 +110,88 @@ 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];
|
||||||
@@ -82,8 +205,13 @@ 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) {
|
||||||
@@ -100,7 +228,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
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) {
|
||||||
@@ -189,26 +317,7 @@ 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");
|
||||||
@@ -216,80 +325,34 @@ run_recorder(void *data) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
bool
|
||||||
recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
|
recorder_start(struct recorder *recorder) {
|
||||||
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");
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
void
|
||||||
recorder_close(struct recorder *recorder) {
|
recorder_stop(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);
|
||||||
|
|
||||||
sc_thread_join(&recorder->thread, NULL);
|
|
||||||
|
|
||||||
avio_close(recorder->ctx->pb);
|
|
||||||
avformat_free_context(recorder->ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
void
|
||||||
|
recorder_join(struct recorder *recorder) {
|
||||||
|
sc_thread_join(&recorder->thread, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
@@ -313,73 +376,3 @@ 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);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#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"
|
||||||
|
|
||||||
@@ -20,8 +19,6 @@ 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;
|
||||||
@@ -31,7 +28,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_close()
|
bool stopped; // set on recorder_stop() by the stream reader
|
||||||
bool failed; // set on packet write failure
|
bool failed; // set on packet write failure
|
||||||
struct recorder_queue queue;
|
struct recorder_queue queue;
|
||||||
|
|
||||||
@@ -49,4 +46,22 @@ 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
|
||||||
|
|||||||
@@ -25,12 +25,14 @@
|
|||||||
#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;
|
||||||
@@ -39,7 +41,7 @@ static struct file_handler file_handler;
|
|||||||
|
|
||||||
static struct input_manager input_manager = {
|
static struct input_manager input_manager = {
|
||||||
.controller = &controller,
|
.controller = &controller,
|
||||||
.fps_counter = &fps_counter,
|
.video_buffer = &video_buffer,
|
||||||
.screen = &screen,
|
.screen = &screen,
|
||||||
.repeat = 0,
|
.repeat = 0,
|
||||||
|
|
||||||
@@ -274,12 +276,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 = {
|
||||||
@@ -297,7 +299,6 @@ 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, ¶ms)) {
|
if (!server_start(&server, options->serial, ¶ms)) {
|
||||||
goto end;
|
goto end;
|
||||||
@@ -331,6 +332,12 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
}
|
}
|
||||||
fps_counter_initialized = true;
|
fps_counter_initialized = true;
|
||||||
|
|
||||||
|
if (!video_buffer_init(&video_buffer, &fps_counter,
|
||||||
|
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)) {
|
||||||
@@ -339,7 +346,7 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
file_handler_initialized = true;
|
file_handler_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
decoder_init(&decoder);
|
decoder_init(&decoder, &video_buffer);
|
||||||
dec = &decoder;
|
dec = &decoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,15 +364,14 @@ 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);
|
stream_init(&stream, server.video_socket, dec, rec);
|
||||||
|
|
||||||
if (dec) {
|
// now we consumed the header values, the socket receives the video stream
|
||||||
stream_add_sink(&stream, &dec->packet_sink);
|
// start the stream
|
||||||
}
|
if (!stream_start(&stream)) {
|
||||||
|
goto end;
|
||||||
if (rec) {
|
|
||||||
stream_add_sink(&stream, &rec->packet_sink);
|
|
||||||
}
|
}
|
||||||
|
stream_started = true;
|
||||||
|
|
||||||
if (options->display) {
|
if (options->display) {
|
||||||
if (options->control) {
|
if (options->control) {
|
||||||
@@ -383,25 +389,16 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
const char *window_title =
|
const char *window_title =
|
||||||
options->window_title ? options->window_title : device_name;
|
options->window_title ? options->window_title : device_name;
|
||||||
|
|
||||||
struct screen_params screen_params = {
|
screen_init(&screen, &video_buffer);
|
||||||
.window_title = window_title,
|
|
||||||
.frame_size = frame_size,
|
|
||||||
.always_on_top = options->always_on_top,
|
|
||||||
.window_x = options->window_x,
|
|
||||||
.window_y = options->window_y,
|
|
||||||
.window_width = options->window_width,
|
|
||||||
.window_height = options->window_height,
|
|
||||||
.window_borderless = options->window_borderless,
|
|
||||||
.rotation = options->rotation,
|
|
||||||
.mipmaps = options->mipmaps,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!screen_init(&screen, &fps_counter, &screen_params)) {
|
if (!screen_init_rendering(&screen, window_title, frame_size,
|
||||||
|
options->always_on_top, options->window_x,
|
||||||
|
options->window_y, options->window_width,
|
||||||
|
options->window_height,
|
||||||
|
options->window_borderless,
|
||||||
|
options->rotation, options->mipmaps)) {
|
||||||
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;
|
||||||
@@ -418,21 +415,19 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// now we consumed the header values, the socket receives the video stream
|
|
||||||
// start the stream
|
|
||||||
if (!stream_start(&stream)) {
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
stream_started = true;
|
|
||||||
|
|
||||||
input_manager_init(&input_manager, options);
|
input_manager_init(&input_manager, options);
|
||||||
|
|
||||||
ret = event_loop(options);
|
ret = event_loop(options);
|
||||||
LOGD("quit...");
|
LOGD("quit...");
|
||||||
|
|
||||||
|
screen_destroy(&screen);
|
||||||
|
|
||||||
end:
|
end:
|
||||||
// The stream is not stopped explicitly, because it will stop by itself on
|
// stop stream and controller so that they don't continue once their socket
|
||||||
// end-of-stream
|
// is shutdown
|
||||||
|
if (stream_started) {
|
||||||
|
stream_stop(&stream);
|
||||||
|
}
|
||||||
if (controller_started) {
|
if (controller_started) {
|
||||||
controller_stop(&controller);
|
controller_stop(&controller);
|
||||||
}
|
}
|
||||||
@@ -453,13 +448,6 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -476,6 +464,10 @@ 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);
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ 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;
|
||||||
@@ -81,7 +82,6 @@ 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 { \
|
||||||
@@ -103,10 +103,10 @@ struct scrcpy_options {
|
|||||||
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
|
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
|
||||||
.count = 2, \
|
.count = 2, \
|
||||||
}, \
|
}, \
|
||||||
.max_size = 0, \
|
.max_size = DEFAULT_MAX_SIZE, \
|
||||||
.bit_rate = DEFAULT_BIT_RATE, \
|
.bit_rate = DEFAULT_BIT_RATE, \
|
||||||
.max_fps = 0, \
|
.max_fps = 0, \
|
||||||
.lock_video_orientation = -1, \
|
.lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \
|
||||||
.rotation = 0, \
|
.rotation = 0, \
|
||||||
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
|
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
|
||||||
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
|
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
|
||||||
@@ -119,6 +119,7 @@ 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, \
|
||||||
@@ -128,7 +129,6 @@ 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
|
||||||
|
|||||||
165
app/src/screen.c
165
app/src/screen.c
@@ -13,8 +13,6 @@
|
|||||||
|
|
||||||
#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;
|
||||||
@@ -193,6 +191,12 @@ screen_update_content_rect(struct screen *screen) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
screen_init(struct screen *screen, struct video_buffer *vb) {
|
||||||
|
*screen = (struct screen) SCREEN_INITIALIZER;
|
||||||
|
screen->vb = vb;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -220,87 +224,27 @@ 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 fps_counter *fps_counter,
|
screen_init_rendering(struct screen *screen, const char *window_title,
|
||||||
const struct screen_params *params) {
|
struct size frame_size, bool always_on_top,
|
||||||
screen->fps_counter = fps_counter;
|
int16_t window_x, int16_t window_y, uint16_t window_width,
|
||||||
|
uint16_t window_height, bool window_borderless,
|
||||||
screen->resize_pending = false;
|
uint8_t rotation, bool mipmaps) {
|
||||||
screen->has_frame = false;
|
screen->frame_size = frame_size;
|
||||||
screen->fullscreen = false;
|
screen->rotation = rotation;
|
||||||
screen->maximized = false;
|
if (rotation) {
|
||||||
|
LOGI("Initial display rotation set to %u", rotation);
|
||||||
bool ok = video_buffer_init(&screen->vb);
|
|
||||||
if (!ok) {
|
|
||||||
LOGE("Could not initialize video buffer");
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
struct size content_size = get_rotated_size(frame_size, screen->rotation);
|
||||||
screen->frame_size = params->frame_size;
|
|
||||||
screen->rotation = params->rotation;
|
|
||||||
if (screen->rotation) {
|
|
||||||
LOGI("Initial display rotation set to %u", screen->rotation);
|
|
||||||
}
|
|
||||||
struct size content_size =
|
|
||||||
get_rotated_size(screen->frame_size, screen->rotation);
|
|
||||||
screen->content_size = content_size;
|
screen->content_size = content_size;
|
||||||
|
|
||||||
struct size window_size = get_initial_optimal_size(content_size,
|
struct size window_size =
|
||||||
params->window_width,
|
get_initial_optimal_size(content_size, window_width, 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 (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;
|
||||||
#else
|
#else
|
||||||
@@ -308,15 +252,15 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter,
|
|||||||
"(compile with SDL >= 2.0.5 to enable it)");
|
"(compile with SDL >= 2.0.5 to enable it)");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
if (params->window_borderless) {
|
if (window_borderless) {
|
||||||
window_flags |= SDL_WINDOW_BORDERLESS;
|
window_flags |= SDL_WINDOW_BORDERLESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED
|
int x = window_x != SC_WINDOW_POSITION_UNDEFINED
|
||||||
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED;
|
? window_x : (int) SDL_WINDOWPOS_UNDEFINED;
|
||||||
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED
|
int y = window_y != SC_WINDOW_POSITION_UNDEFINED
|
||||||
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED;
|
? window_y : (int) SDL_WINDOWPOS_UNDEFINED;
|
||||||
screen->window = SDL_CreateWindow(params->window_title, x, y,
|
screen->window = SDL_CreateWindow(window_title, x, y,
|
||||||
window_size.width, window_size.height,
|
window_size.width, window_size.height,
|
||||||
window_flags);
|
window_flags);
|
||||||
if (!screen->window) {
|
if (!screen->window) {
|
||||||
@@ -329,7 +273,6 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter,
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,8 +281,6 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter,
|
|||||||
const char *renderer_name = r ? NULL : renderer_info.name;
|
const char *renderer_name = r ? NULL : renderer_info.name;
|
||||||
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
||||||
|
|
||||||
screen->mipmaps = false;
|
|
||||||
|
|
||||||
// starts with "opengl"
|
// starts with "opengl"
|
||||||
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||||
if (use_opengl) {
|
if (use_opengl) {
|
||||||
@@ -348,7 +289,7 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter,
|
|||||||
|
|
||||||
LOGI("OpenGL version: %s", gl->version);
|
LOGI("OpenGL version: %s", gl->version);
|
||||||
|
|
||||||
if (params->mipmaps) {
|
if (mipmaps) {
|
||||||
bool supports_mipmaps =
|
bool supports_mipmaps =
|
||||||
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
|
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
|
||||||
2, 0 /* OpenGL ES 2.0+ */);
|
2, 0 /* OpenGL ES 2.0+ */);
|
||||||
@@ -362,7 +303,7 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter,
|
|||||||
} else {
|
} else {
|
||||||
LOGI("Trilinear filtering disabled");
|
LOGI("Trilinear filtering disabled");
|
||||||
}
|
}
|
||||||
} else if (params->mipmaps) {
|
} else if (mipmaps) {
|
||||||
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,24 +315,14 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter,
|
|||||||
LOGW("Could not load icon");
|
LOGW("Could not load icon");
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, params->frame_size.width,
|
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width,
|
||||||
params->frame_size.height);
|
frame_size.height);
|
||||||
screen->texture = create_texture(screen);
|
screen->texture = create_texture(screen);
|
||||||
if (!screen->texture) {
|
if (!screen->texture) {
|
||||||
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);
|
screen_destroy(screen);
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,18 +333,6 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter,
|
|||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,14 +343,11 @@ screen_show_window(struct screen *screen) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
screen_destroy(struct screen *screen) {
|
screen_destroy(struct screen *screen) {
|
||||||
#ifndef NDEBUG
|
if (screen->texture) {
|
||||||
assert(!screen->open);
|
SDL_DestroyTexture(screen->texture);
|
||||||
#endif
|
}
|
||||||
av_frame_free(&screen->frame);
|
|
||||||
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
|
||||||
@@ -536,12 +452,7 @@ update_texture(struct screen *screen, const AVFrame *frame) {
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
screen_update_frame(struct screen *screen) {
|
screen_update_frame(struct screen *screen) {
|
||||||
av_frame_unref(screen->frame);
|
const AVFrame *frame = video_buffer_take_rendering_frame(screen->vb);
|
||||||
video_buffer_consume(&screen->vb, screen->frame);
|
|
||||||
AVFrame *frame = screen->frame;
|
|
||||||
|
|
||||||
fps_counter_add_rendered_frame(screen->fps_counter);
|
|
||||||
|
|
||||||
struct size new_frame_size = {frame->width, frame->height};
|
struct size new_frame_size = {frame->width, frame->height};
|
||||||
if (!prepare_for_frame(screen, new_frame_size)) {
|
if (!prepare_for_frame(screen, new_frame_size)) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -9,21 +9,11 @@
|
|||||||
|
|
||||||
#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 sc_frame_sink frame_sink; // frame sink trait
|
struct video_buffer *vb;
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
bool open; // track the open/close state to assert correct behavior
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct video_buffer vb;
|
|
||||||
struct fps_counter *fps_counter;
|
|
||||||
|
|
||||||
SDL_Window *window;
|
SDL_Window *window;
|
||||||
SDL_Renderer *renderer;
|
SDL_Renderer *renderer;
|
||||||
SDL_Texture *texture;
|
SDL_Texture *texture;
|
||||||
@@ -43,31 +33,55 @@ struct screen {
|
|||||||
bool has_frame;
|
bool has_frame;
|
||||||
bool fullscreen;
|
bool fullscreen;
|
||||||
bool maximized;
|
bool maximized;
|
||||||
bool mipmaps;
|
bool no_window;
|
||||||
|
|
||||||
AVFrame *frame;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct screen_params {
|
|
||||||
const char *window_title;
|
|
||||||
struct size frame_size;
|
|
||||||
bool always_on_top;
|
|
||||||
|
|
||||||
int16_t window_x;
|
|
||||||
int16_t window_y;
|
|
||||||
uint16_t window_width; // accepts SC_WINDOW_POSITION_UNDEFINED
|
|
||||||
uint16_t window_height; // accepts SC_WINDOW_POSITION_UNDEFINED
|
|
||||||
|
|
||||||
bool window_borderless;
|
|
||||||
|
|
||||||
uint8_t rotation;
|
|
||||||
bool mipmaps;
|
bool mipmaps;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define SCREEN_INITIALIZER { \
|
||||||
|
.vb = NULL, \
|
||||||
|
.window = NULL, \
|
||||||
|
.renderer = NULL, \
|
||||||
|
.texture = NULL, \
|
||||||
|
.gl = {0}, \
|
||||||
|
.frame_size = { \
|
||||||
|
.width = 0, \
|
||||||
|
.height = 0, \
|
||||||
|
}, \
|
||||||
|
.content_size = { \
|
||||||
|
.width = 0, \
|
||||||
|
.height = 0, \
|
||||||
|
}, \
|
||||||
|
.resize_pending = false, \
|
||||||
|
.windowed_content_size = { \
|
||||||
|
.width = 0, \
|
||||||
|
.height = 0, \
|
||||||
|
}, \
|
||||||
|
.rotation = 0, \
|
||||||
|
.rect = { \
|
||||||
|
.x = 0, \
|
||||||
|
.y = 0, \
|
||||||
|
.w = 0, \
|
||||||
|
.h = 0, \
|
||||||
|
}, \
|
||||||
|
.has_frame = false, \
|
||||||
|
.fullscreen = false, \
|
||||||
|
.maximized = false, \
|
||||||
|
.no_window = false, \
|
||||||
|
.mipmaps = false, \
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize default values
|
||||||
|
void
|
||||||
|
screen_init(struct screen *screen, struct video_buffer *vb);
|
||||||
|
|
||||||
// initialize screen, create window, renderer and texture (window is hidden)
|
// initialize screen, create window, renderer and texture (window is hidden)
|
||||||
|
// window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED
|
||||||
bool
|
bool
|
||||||
screen_init(struct screen *screen, struct fps_counter *fps_counter,
|
screen_init_rendering(struct screen *screen, const char *window_title,
|
||||||
const struct screen_params *params);
|
struct size frame_size, bool always_on_top,
|
||||||
|
int16_t window_x, int16_t window_y, uint16_t window_width,
|
||||||
|
uint16_t window_height, bool window_borderless,
|
||||||
|
uint8_t rotation, bool mipmaps);
|
||||||
|
|
||||||
// show the window
|
// show the window
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -293,7 +293,6 @@ 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 "
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ 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
|
||||||
|
|||||||
114
app/src/stream.c
114
app/src/stream.c
@@ -66,11 +66,25 @@ notify_stopped(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
push_packet_to_sinks(struct stream *stream, const AVPacket *packet) {
|
process_config_packet(struct stream *stream, AVPacket *packet) {
|
||||||
for (unsigned i = 0; i < stream->sink_count; ++i) {
|
if (stream->recorder && !recorder_push(stream->recorder, packet)) {
|
||||||
struct sc_packet_sink *sink = stream->sinks[i];
|
LOGE("Could not send config packet to recorder");
|
||||||
if (!sink->ops->push(sink, packet)) {
|
return false;
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,11 +111,9 @@ stream_parse(struct stream *stream, AVPacket *packet) {
|
|||||||
packet->flags |= AV_PKT_FLAG_KEY;
|
packet->flags |= AV_PKT_FLAG_KEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
packet->dts = packet->pts;
|
bool ok = process_frame(stream, packet);
|
||||||
|
|
||||||
bool ok = push_packet_to_sinks(stream, packet);
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not process packet");
|
LOGE("Could not process frame");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +156,7 @@ stream_push_packet(struct stream *stream, AVPacket *packet) {
|
|||||||
|
|
||||||
if (is_config) {
|
if (is_config) {
|
||||||
// config packet
|
// config packet
|
||||||
bool ok = push_packet_to_sinks(stream, packet);
|
bool ok = process_config_packet(stream, packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -165,33 +177,6 @@ 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;
|
||||||
@@ -208,15 +193,27 @@ run_stream(void *data) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!stream_open_sinks(stream, codec)) {
|
if (stream->decoder && !decoder_open(stream->decoder, codec)) {
|
||||||
LOGE("Could not open stream sinks");
|
LOGE("Could not open decoder");
|
||||||
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_close_sinks;
|
goto finally_stop_and_join_recorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We must only pass complete frames to av_parser_parse2()!
|
// We must only pass complete frames to av_parser_parse2()!
|
||||||
@@ -246,8 +243,20 @@ run_stream(void *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
av_parser_close(stream->parser);
|
av_parser_close(stream->parser);
|
||||||
finally_close_sinks:
|
finally_stop_and_join_recorder:
|
||||||
stream_close_sinks(stream);
|
if (stream->recorder) {
|
||||||
|
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:
|
||||||
@@ -256,18 +265,12 @@ 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
|
||||||
@@ -282,6 +285,13 @@ 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);
|
||||||
|
|||||||
@@ -8,19 +8,16 @@
|
|||||||
#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"
|
||||||
|
|
||||||
#define STREAM_MAX_SINKS 2
|
struct video_buffer;
|
||||||
|
|
||||||
struct stream {
|
struct stream {
|
||||||
socket_t socket;
|
socket_t socket;
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
|
struct decoder *decoder;
|
||||||
struct sc_packet_sink *sinks[STREAM_MAX_SINKS];
|
struct recorder *recorder;
|
||||||
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
|
||||||
@@ -30,14 +27,15 @@ 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);
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -7,82 +7,133 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
video_buffer_init(struct video_buffer *vb) {
|
video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
|
||||||
vb->pending_frame = av_frame_alloc();
|
bool render_expired_frames) {
|
||||||
if (!vb->pending_frame) {
|
vb->fps_counter = fps_counter;
|
||||||
return false;
|
|
||||||
|
vb->decoding_frame = av_frame_alloc();
|
||||||
|
if (!vb->decoding_frame) {
|
||||||
|
goto error_0;
|
||||||
}
|
}
|
||||||
|
|
||||||
vb->tmp_frame = av_frame_alloc();
|
vb->pending_frame = av_frame_alloc();
|
||||||
if (!vb->tmp_frame) {
|
if (!vb->pending_frame) {
|
||||||
av_frame_free(&vb->pending_frame);
|
goto error_1;
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
vb->rendering_frame = av_frame_alloc();
|
||||||
|
if (!vb->rendering_frame) {
|
||||||
|
goto error_2;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ok = sc_mutex_init(&vb->mutex);
|
bool ok = sc_mutex_init(&vb->mutex);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
av_frame_free(&vb->pending_frame);
|
goto error_3;
|
||||||
av_frame_free(&vb->tmp_frame);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// there is initially no frame, so consider it has already been consumed
|
vb->render_expired_frames = render_expired_frames;
|
||||||
|
if (render_expired_frames) {
|
||||||
|
ok = sc_cond_init(&vb->pending_frame_consumed_cond);
|
||||||
|
if (!ok) {
|
||||||
|
sc_mutex_destroy(&vb->mutex);
|
||||||
|
goto error_2;
|
||||||
|
}
|
||||||
|
// interrupted is not used if expired frames are not rendered
|
||||||
|
// since offering a frame will never block
|
||||||
|
vb->interrupted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// there is initially no rendering frame, so consider it has already been
|
||||||
|
// consumed
|
||||||
vb->pending_frame_consumed = true;
|
vb->pending_frame_consumed = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
error_3:
|
||||||
|
av_frame_free(&vb->rendering_frame);
|
||||||
|
error_2:
|
||||||
|
av_frame_free(&vb->pending_frame);
|
||||||
|
error_1:
|
||||||
|
av_frame_free(&vb->decoding_frame);
|
||||||
|
error_0:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
video_buffer_destroy(struct video_buffer *vb) {
|
video_buffer_destroy(struct video_buffer *vb) {
|
||||||
|
if (vb->render_expired_frames) {
|
||||||
|
sc_cond_destroy(&vb->pending_frame_consumed_cond);
|
||||||
|
}
|
||||||
sc_mutex_destroy(&vb->mutex);
|
sc_mutex_destroy(&vb->mutex);
|
||||||
|
av_frame_free(&vb->rendering_frame);
|
||||||
av_frame_free(&vb->pending_frame);
|
av_frame_free(&vb->pending_frame);
|
||||||
av_frame_free(&vb->tmp_frame);
|
av_frame_free(&vb->decoding_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static void
|
||||||
swap_frames(AVFrame **lhs, AVFrame **rhs) {
|
video_buffer_swap_decoding_frame(struct video_buffer *vb) {
|
||||||
AVFrame *tmp = *lhs;
|
sc_mutex_assert(&vb->mutex);
|
||||||
*lhs = *rhs;
|
AVFrame *tmp = vb->decoding_frame;
|
||||||
*rhs = tmp;
|
vb->decoding_frame = vb->pending_frame;
|
||||||
|
vb->pending_frame = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
static void
|
||||||
video_buffer_push(struct video_buffer *vb, const AVFrame *frame,
|
video_buffer_swap_rendering_frame(struct video_buffer *vb) {
|
||||||
bool *previous_frame_skipped) {
|
sc_mutex_assert(&vb->mutex);
|
||||||
sc_mutex_lock(&vb->mutex);
|
AVFrame *tmp = vb->rendering_frame;
|
||||||
|
vb->rendering_frame = vb->pending_frame;
|
||||||
// Use a temporary frame to preserve pending_frame in case of error.
|
vb->pending_frame = tmp;
|
||||||
// tmp_frame is an empty frame, no need to call av_frame_unref() beforehand.
|
|
||||||
int r = av_frame_ref(vb->tmp_frame, frame);
|
|
||||||
if (r) {
|
|
||||||
LOGE("Could not ref frame: %d", r);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now that av_frame_ref() succeeded, we can replace the previous
|
|
||||||
// pending_frame
|
|
||||||
swap_frames(&vb->pending_frame, &vb->tmp_frame);
|
|
||||||
av_frame_unref(vb->tmp_frame);
|
|
||||||
|
|
||||||
if (previous_frame_skipped) {
|
|
||||||
*previous_frame_skipped = !vb->pending_frame_consumed;
|
|
||||||
}
|
|
||||||
vb->pending_frame_consumed = false;
|
|
||||||
|
|
||||||
sc_mutex_unlock(&vb->mutex);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
video_buffer_consume(struct video_buffer *vb, AVFrame *dst) {
|
video_buffer_offer_decoded_frame(struct video_buffer *vb,
|
||||||
|
bool *previous_frame_skipped) {
|
||||||
|
sc_mutex_lock(&vb->mutex);
|
||||||
|
if (vb->render_expired_frames) {
|
||||||
|
// wait for the current (expired) frame to be consumed
|
||||||
|
while (!vb->pending_frame_consumed && !vb->interrupted) {
|
||||||
|
sc_cond_wait(&vb->pending_frame_consumed_cond, &vb->mutex);
|
||||||
|
}
|
||||||
|
} else if (!vb->pending_frame_consumed) {
|
||||||
|
fps_counter_add_skipped_frame(vb->fps_counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
video_buffer_swap_decoding_frame(vb);
|
||||||
|
|
||||||
|
*previous_frame_skipped = !vb->pending_frame_consumed;
|
||||||
|
vb->pending_frame_consumed = false;
|
||||||
|
|
||||||
|
sc_mutex_unlock(&vb->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVFrame *
|
||||||
|
video_buffer_take_rendering_frame(struct video_buffer *vb) {
|
||||||
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;
|
||||||
|
|
||||||
av_frame_move_ref(dst, vb->pending_frame);
|
fps_counter_add_rendered_frame(vb->fps_counter);
|
||||||
// av_frame_move_ref() resets its source frame, so no need to call
|
|
||||||
// av_frame_unref()
|
|
||||||
|
|
||||||
|
video_buffer_swap_rendering_frame(vb);
|
||||||
|
|
||||||
|
if (vb->render_expired_frames) {
|
||||||
|
// unblock video_buffer_offer_decoded_frame()
|
||||||
|
sc_cond_signal(&vb->pending_frame_consumed_cond);
|
||||||
|
}
|
||||||
sc_mutex_unlock(&vb->mutex);
|
sc_mutex_unlock(&vb->mutex);
|
||||||
|
|
||||||
|
// rendering_frame is only written from this thread, no need to lock
|
||||||
|
return vb->rendering_frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
video_buffer_interrupt(struct video_buffer *vb) {
|
||||||
|
if (vb->render_expired_frames) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,39 +12,57 @@
|
|||||||
typedef struct AVFrame AVFrame;
|
typedef struct AVFrame AVFrame;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A video buffer holds 1 pending frame, which is the last frame received from
|
* There are 3 frames in memory:
|
||||||
* the producer (typically, the decoder).
|
* - one frame is held by the decoder (decoding_frame)
|
||||||
|
* - one frame is held by the renderer (rendering_frame)
|
||||||
|
* - one frame is shared between the decoder and the renderer (pending_frame)
|
||||||
*
|
*
|
||||||
* If a pending frame has not been consumed when the producer pushes a new
|
* The decoder decodes a packet into the decoding_frame (it may takes time).
|
||||||
* frame, then it is lost. The intent is to always provide access to the very
|
|
||||||
* last frame to minimize latency.
|
|
||||||
*
|
*
|
||||||
* The producer and the consumer typically do not live in the same thread.
|
* Once the frame is decoded, it calls video_buffer_offer_decoded_frame(),
|
||||||
* That's the reason why the callback on_frame_available() does not provide the
|
* which swaps the decoding and pending frames.
|
||||||
* frame as parameter: the consumer might post an event to its own thread to
|
*
|
||||||
* retrieve the pending frame from there, and that frame may have changed since
|
* When the renderer is notified that a new frame is available, it calls
|
||||||
* the callback if producer pushed a new one in between.
|
* video_buffer_take_rendering_frame() to retrieve it, which swaps the pending
|
||||||
|
* and rendering frames. The frame is valid until the next call, without
|
||||||
|
* blocking the decoder.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
struct video_buffer {
|
struct video_buffer {
|
||||||
|
AVFrame *decoding_frame;
|
||||||
AVFrame *pending_frame;
|
AVFrame *pending_frame;
|
||||||
AVFrame *tmp_frame; // To preserve the pending frame on error
|
AVFrame *rendering_frame;
|
||||||
|
|
||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
|
bool render_expired_frames;
|
||||||
|
bool interrupted;
|
||||||
|
|
||||||
|
sc_cond pending_frame_consumed_cond;
|
||||||
bool pending_frame_consumed;
|
bool pending_frame_consumed;
|
||||||
|
|
||||||
|
struct fps_counter *fps_counter;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
video_buffer_init(struct video_buffer *vb);
|
video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
|
||||||
|
bool render_expired_frames);
|
||||||
|
|
||||||
void
|
void
|
||||||
video_buffer_destroy(struct video_buffer *vb);
|
video_buffer_destroy(struct video_buffer *vb);
|
||||||
|
|
||||||
bool
|
// set the decoded frame as ready for rendering
|
||||||
video_buffer_push(struct video_buffer *vb, const AVFrame *frame, bool *skipped);
|
// the output flag is set to report whether the previous frame has been skipped
|
||||||
|
|
||||||
void
|
void
|
||||||
video_buffer_consume(struct video_buffer *vb, AVFrame *dst);
|
video_buffer_offer_decoded_frame(struct video_buffer *vb,
|
||||||
|
bool *previous_frame_skipped);
|
||||||
|
|
||||||
|
// mark the rendering frame as consumed and return it
|
||||||
|
// the frame is valid until the next call to this function
|
||||||
|
const AVFrame *
|
||||||
|
video_buffer_take_rendering_frame(struct video_buffer *vb);
|
||||||
|
|
||||||
|
// wake up and avoid any blocking call
|
||||||
|
void
|
||||||
|
video_buffer_interrupt(struct video_buffer *vb);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ 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",
|
||||||
@@ -86,6 +87,7 @@ 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);
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
[binaries]
|
[binaries]
|
||||||
name = 'mingw'
|
name = 'mingw'
|
||||||
c = 'i686-w64-mingw32-gcc'
|
c = '/usr/bin/i686-w64-mingw32-gcc'
|
||||||
cpp = 'i686-w64-mingw32-g++'
|
cpp = '/usr/bin/i686-w64-mingw32-g++'
|
||||||
ar = 'i686-w64-mingw32-ar'
|
ar = '/usr/bin/i686-w64-mingw32-ar'
|
||||||
strip = 'i686-w64-mingw32-strip'
|
strip = '/usr/bin/i686-w64-mingw32-strip'
|
||||||
pkgconfig = 'i686-w64-mingw32-pkg-config'
|
pkgconfig = '/usr/bin/i686-w64-mingw32-pkg-config'
|
||||||
|
|
||||||
[host_machine]
|
[host_machine]
|
||||||
system = 'windows'
|
system = 'windows'
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
[binaries]
|
[binaries]
|
||||||
name = 'mingw'
|
name = 'mingw'
|
||||||
c = 'x86_64-w64-mingw32-gcc'
|
c = '/usr/bin/x86_64-w64-mingw32-gcc'
|
||||||
cpp = 'x86_64-w64-mingw32-g++'
|
cpp = '/usr/bin/x86_64-w64-mingw32-g++'
|
||||||
ar = 'x86_64-w64-mingw32-ar'
|
ar = '/usr/bin/x86_64-w64-mingw32-ar'
|
||||||
strip = 'x86_64-w64-mingw32-strip'
|
strip = '/usr/bin/x86_64-w64-mingw32-strip'
|
||||||
pkgconfig = 'x86_64-w64-mingw32-pkg-config'
|
pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config'
|
||||||
|
|
||||||
[host_machine]
|
[host_machine]
|
||||||
system = 'windows'
|
system = 'windows'
|
||||||
|
|||||||
@@ -1,7 +1 @@
|
|||||||
strCommand = "cmd /c scrcpy.exe"
|
CreateObject("Wscript.Shell").Run "cmd /c scrcpy.exe", 0, false
|
||||||
|
|
||||||
For Each Arg In WScript.Arguments
|
|
||||||
strCommand = strCommand & " """ & replace(Arg, """", """""""""") & """"
|
|
||||||
Next
|
|
||||||
|
|
||||||
CreateObject("Wscript.Shell").Run strCommand, 0, false
|
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ 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")')
|
||||||
|
|||||||
@@ -19,21 +19,19 @@ public final class CleanUp {
|
|||||||
// not instantiable
|
// not instantiable
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode, boolean powerOffScreen, int displayId)
|
public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException {
|
||||||
throws IOException {
|
boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode;
|
||||||
boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode || powerOffScreen;
|
|
||||||
if (needProcess) {
|
if (needProcess) {
|
||||||
startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode, powerOffScreen, displayId);
|
startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode);
|
||||||
} 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, boolean powerOffScreen,
|
private static void startProcess(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException {
|
||||||
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), String.valueOf(powerOffScreen), String.valueOf(displayId)};
|
restoreStayOn), String.valueOf(restoreNormalPowerMode)};
|
||||||
|
|
||||||
ProcessBuilder builder = new ProcessBuilder(cmd);
|
ProcessBuilder builder = new ProcessBuilder(cmd);
|
||||||
builder.environment().put("CLASSPATH", SERVER_PATH);
|
builder.environment().put("CLASSPATH", SERVER_PATH);
|
||||||
@@ -63,8 +61,6 @@ 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();
|
||||||
@@ -80,12 +76,9 @@ public final class CleanUp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Device.isScreenOn()) {
|
if (restoreNormalPowerMode) {
|
||||||
if (powerOffScreen) {
|
Ln.i("Restoring normal power mode");
|
||||||
Ln.i("Power off screen");
|
if (Device.isScreenOn()) {
|
||||||
Device.powerOffScreen(displayId);
|
|
||||||
} else if (restoreNormalPowerMode) {
|
|
||||||
Ln.i("Restoring normal power mode");
|
|
||||||
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
|
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
|
|
||||||
public class Controller {
|
public class Controller {
|
||||||
|
|
||||||
private static final int DEFAULT_DEVICE_ID = 0;
|
private static final int DEVICE_ID_VIRTUAL = -1;
|
||||||
|
|
||||||
private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor();
|
private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ public class Controller {
|
|||||||
|
|
||||||
MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
|
MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
|
||||||
coords.orientation = 0;
|
coords.orientation = 0;
|
||||||
coords.size = 0;
|
coords.size = 1;
|
||||||
|
|
||||||
pointerProperties[i] = props;
|
pointerProperties[i] = props;
|
||||||
pointerCoords[i] = coords;
|
pointerCoords[i] = coords;
|
||||||
@@ -208,13 +208,9 @@ 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, DEVICE_ID_VIRTUAL, 0, source,
|
||||||
0);
|
0);
|
||||||
return device.injectEvent(event);
|
return device.injectEvent(event);
|
||||||
}
|
}
|
||||||
@@ -237,7 +233,7 @@ public class Controller {
|
|||||||
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
|
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
|
||||||
|
|
||||||
MotionEvent event = MotionEvent
|
MotionEvent event = MotionEvent
|
||||||
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0,
|
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
|
||||||
InputDevice.SOURCE_TOUCHSCREEN, 0);
|
InputDevice.SOURCE_TOUCHSCREEN, 0);
|
||||||
return device.injectEvent(event);
|
return device.injectEvent(event);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,17 +153,13 @@ 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 static boolean injectEvent(InputEvent inputEvent, int mode, int displayId) {
|
public boolean injectEvent(InputEvent inputEvent, int mode) {
|
||||||
if (!supportsInputEvents(displayId)) {
|
if (!supportsInputEvents()) {
|
||||||
return false;
|
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) {
|
if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) {
|
||||||
@@ -173,29 +169,10 @@ 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,
|
||||||
@@ -203,10 +180,6 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -276,13 +249,6 @@ 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).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ 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;
|
||||||
@@ -130,12 +129,4 @@ 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.media.MediaCodec;
|
|||||||
import android.media.MediaCodecInfo;
|
import android.media.MediaCodecInfo;
|
||||||
import android.media.MediaCodecList;
|
import android.media.MediaCodecList;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
|
||||||
@@ -225,7 +226,9 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static IBinder createDisplay() {
|
private static IBinder createDisplay() {
|
||||||
return SurfaceControl.createDisplay("scrcpy", true);
|
// Since Android 12, secure displays could not be created with shell permissions anymore
|
||||||
|
boolean secure = Build.VERSION.SDK_INT <= Build.VERSION_CODES.R;
|
||||||
|
return SurfaceControl.createDisplay("scrcpy", secure);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void configure(MediaCodec codec, MediaFormat format) {
|
private static void configure(MediaCodec codec, MediaFormat format) {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public final class Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn, true, options.getPowerOffScreenOnClose(), options.getDisplayId());
|
CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn, true);
|
||||||
|
|
||||||
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 = 16;
|
final int expectedParameters = 15;
|
||||||
if (args.length != expectedParameters) {
|
if (args.length != expectedParameters) {
|
||||||
throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters");
|
throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters");
|
||||||
}
|
}
|
||||||
@@ -185,9 +185,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +230,7 @@ public final class Server {
|
|||||||
if (encoders != null && encoders.length > 0) {
|
if (encoders != null && encoders.length > 0) {
|
||||||
Ln.e("Try to use one of the available encoders:");
|
Ln.e("Try to use one of the available encoders:");
|
||||||
for (MediaCodecInfo encoder : encoders) {
|
for (MediaCodecInfo encoder : encoders) {
|
||||||
Ln.e(" scrcpy --encoder '" + encoder.getName() + "'");
|
Ln.e(" scrcpy --encoder-name '" + encoder.getName() + "'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user