Compare commits

..

1 Commits

Author SHA1 Message Date
Yan
e471ecf204 Turn device screen off after set up
Sometimes it can take quite a while for everything to get set up and
the screen to appear.

PR #3902 <https://github.com/Genymobile/scrcpy/pull/3902>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-04-12 21:37:42 +02:00
30 changed files with 204 additions and 536 deletions

View File

@@ -33,11 +33,10 @@ _scrcpy() {
--no-clipboard-autosync
--no-downsize-on-error
-n --no-control
-N --no-mirror
-N --no-display
--no-key-repeat
--no-mipmaps
--no-power-on
--no-video
--otg
-p --port=
--power-off-on-close

View File

@@ -39,11 +39,10 @@ arguments=(
'--no-clipboard-autosync[Disable automatic clipboard synchronization]'
'--no-downsize-on-error[Disable lowering definition on MediaCodec error]'
{-n,--no-control}'[Disable device control \(mirror the device in read only\)]'
{-N,--no-mirror}'[Do not mirror device \(only when recording or V4L2 sink is enabled\)]'
{-N,--no-display}'[Do not display device \(during screen recording or when V4L2 sink is enabled\)]'
'--no-key-repeat[Do not forward repeated key events when a key is held down]'
'--no-mipmaps[Disable the generation of mipmaps]'
'--no-power-on[Do not power on the device on start]'
'--no-video[Disable video forwarding]'
'--otg[Run in OTG mode \(simulating physical keyboard and mouse\)]'
{-p,--port=}'[\[port\[\:port\]\] Set the TCP port \(range\) used by the client to listen]'
'--power-off-on-close[Turn the device screen off when closing scrcpy]'

View File

@@ -6,11 +6,11 @@ cd "$DIR"
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
VERSION=6.0-scrcpy-3
VERSION=6.0-scrcpy-2
DEP_DIR="ffmpeg-$VERSION"
FILENAME="$DEP_DIR".7z
SHA256SUM=36829d98ac4454d7092c72ddb92faa20b60450bc0fe8873076efb0858cdcbc2c
SHA256SUM=98ef97f8607c97a5c4f9c5a0a991b78f105d002a3619145011d16ffb92501b14
if [[ -d "$DEP_DIR" ]]
then

View File

@@ -210,8 +210,8 @@ This option disables this behavior.
Disable device control (mirror the device in read\-only).
.TP
.B \-N, \-\-no\-mirror
Do not mirror device video or audio on the computer (only when recording or V4L2 sink is enabled).
.B \-N, \-\-no\-display
Do not display device (only when screen recording is enabled).
.TP
.B \-\-no\-key\-repeat
@@ -225,10 +225,6 @@ If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically
.B \-\-no\-power\-on
Do not power on the device on start.
.TP
.B \-\-no\-video
Disable video forwarding.
.TP
.B \-\-otg
Run in OTG mode: simulate physical keyboard and mouse, as if the computer keyboard and mouse were plugged directly to the device via an OTG cable.

View File

@@ -72,8 +72,6 @@ enum {
OPT_REQUIRE_AUDIO,
OPT_AUDIO_BUFFER,
OPT_AUDIO_OUTPUT_BUFFER,
OPT_NO_DISPLAY,
OPT_NO_VIDEO,
};
struct sc_option {
@@ -382,14 +380,9 @@ static const struct sc_option options[] = {
},
{
.shortopt = 'N',
.longopt = "no-mirror",
.text = "Do not mirror device video or audio on the computer (only "
"when recording or V4L2 sink is enabled).",
},
{
// deprecated
.longopt_id = OPT_NO_DISPLAY,
.longopt = "no-display",
.text = "Do not display device (only when screen recording or V4L2 "
"sink is enabled).",
},
{
.longopt_id = OPT_NO_KEY_REPEAT,
@@ -408,11 +401,6 @@ static const struct sc_option options[] = {
.longopt = "no-power-on",
.text = "Do not power on the device on start.",
},
{
.longopt_id = OPT_NO_VIDEO,
.longopt = "no-video",
.text = "Disable video forwarding.",
},
{
.longopt_id = OPT_OTG,
.longopt = "otg",
@@ -1479,39 +1467,18 @@ sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
}
#endif
static enum sc_record_format
get_record_format(const char *name) {
if (!strcmp(name, "mp4")) {
return SC_RECORD_FORMAT_MP4;
}
if (!strcmp(name, "mkv")) {
return SC_RECORD_FORMAT_MKV;
}
if (!strcmp(name, "m4a")) {
return SC_RECORD_FORMAT_M4A;
}
if (!strcmp(name, "mka")) {
return SC_RECORD_FORMAT_MKA;
}
if (!strcmp(name, "opus")) {
return SC_RECORD_FORMAT_OPUS;
}
if (!strcmp(name, "aac")) {
return SC_RECORD_FORMAT_AAC;
}
return 0;
}
static bool
parse_record_format(const char *optarg, enum sc_record_format *format) {
enum sc_record_format fmt = get_record_format(optarg);
if (!fmt) {
LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg);
return false;
if (!strcmp(optarg, "mp4")) {
*format = SC_RECORD_FORMAT_MP4;
return true;
}
*format = fmt;
return true;
if (!strcmp(optarg, "mkv")) {
*format = SC_RECORD_FORMAT_MKV;
return true;
}
LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg);
return false;
}
static bool
@@ -1531,13 +1498,18 @@ parse_port(const char *optarg, uint16_t *port) {
static enum sc_record_format
guess_record_format(const char *filename) {
const char *dot = strrchr(filename, '.');
if (!dot) {
size_t len = strlen(filename);
if (len < 4) {
return 0;
}
const char *ext = dot + 1;
return get_record_format(ext);
const char *ext = &filename[len - 4];
if (!strcmp(ext, ".mp4")) {
return SC_RECORD_FORMAT_MP4;
}
if (!strcmp(ext, ".mkv")) {
return SC_RECORD_FORMAT_MKV;
}
return 0;
}
static bool
@@ -1670,11 +1642,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case 'n':
opts->control = false;
break;
case OPT_NO_DISPLAY:
LOGW("--no-display is deprecated, use --no-mirror instead.");
// fall through
case 'N':
opts->mirror = false;
opts->display = false;
break;
case 'p':
if (!parse_port_range(optarg, &opts->port_range)) {
@@ -1819,9 +1788,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_NO_DOWNSIZE_ON_ERROR:
opts->downsize_on_error = false;
break;
case OPT_NO_VIDEO:
opts->video = false;
break;
case OPT_NO_AUDIO:
opts->audio = false;
break;
@@ -1924,8 +1890,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
#ifdef HAVE_V4L2
if (!opts->mirror && !opts->record_filename && !opts->v4l2_device) {
LOGE("-N/--no-mirror requires either screen recording (-r/--record)"
if (!opts->display && !opts->record_filename && !opts->v4l2_device) {
LOGE("-N/--no-display requires either screen recording (-r/--record)"
" or sink to v4l2loopback device (--v4l2-sink)");
return false;
}
@@ -1949,14 +1915,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false;
}
#else
if (!opts->mirror && !opts->record_filename) {
LOGE("-N/--no-mirror requires screen recording (-r/--record)");
if (!opts->display && !opts->record_filename) {
LOGE("-N/--no-display requires screen recording (-r/--record)");
return false;
}
#endif
if (opts->audio && !opts->mirror && !opts->record_filename) {
LOGI("No mirror and no recording: audio disabled");
if (opts->audio && !opts->display && !opts->record_filename) {
LOGI("No display and no recording: audio disabled");
opts->audio = false;
}
@@ -1971,41 +1937,19 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false;
}
if (opts->record_filename) {
if (opts->record_filename && !opts->record_format) {
opts->record_format = guess_record_format(opts->record_filename);
if (!opts->record_format) {
opts->record_format = guess_record_format(opts->record_filename);
if (!opts->record_format) {
LOGE("No format specified for \"%s\" "
"(try with --record-format=mkv)",
opts->record_filename);
return false;
}
}
if (opts->audio_codec == SC_CODEC_RAW) {
LOGW("Recording does not support RAW audio codec");
LOGE("No format specified for \"%s\" "
"(try with --record-format=mkv)",
opts->record_filename);
return false;
}
}
if (opts->video
&& sc_record_format_is_audio_only(opts->record_format)) {
LOGE("Audio container does not support video stream");
return false;
}
if (opts->record_format == SC_RECORD_FORMAT_OPUS
&& opts->audio_codec != SC_CODEC_OPUS) {
LOGE("Recording to OPUS file requires an OPUS audio stream "
"(try with --audio-codec=opus)");
return false;
}
if (opts->record_format == SC_RECORD_FORMAT_AAC
&& opts->audio_codec != SC_CODEC_AAC) {
LOGE("Recording to AAC file requires an AAC audio stream "
"(try with --audio-codec=aac)");
return false;
}
if (opts->record_filename && opts->audio_codec == SC_CODEC_RAW) {
LOGW("Recording does not support RAW audio codec");
return false;
}
if (opts->audio_codec == SC_CODEC_RAW) {
@@ -2088,21 +2032,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
#endif
#ifdef HAVE_USB
if (!(opts->mirror && opts->video) && !opts->otg) {
#else
if (!(opts->mirror && opts->video)) {
#endif
// If video mirroring is disabled and OTG are disabled, then there is
// no way to control the device.
opts->control = false;
}
if (!opts->video) {
// If video is disabled, then scrcpy must exit on audio failure.
opts->require_audio = true;
}
return true;
}

View File

@@ -25,12 +25,6 @@
# define SCRCPY_LAVF_REQUIRES_REGISTER_ALL
#endif
// Not documented in ffmpeg/doc/APIchanges, but AV_CODEC_ID_AV1 has been added
// by FFmpeg commit d42809f9835a4e9e5c7c63210abb09ad0ef19cfb (included in tag
// n3.3).
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 89, 100)
# define SCRCPY_LAVC_HAS_AV1
#endif
// In ffmpeg/doc/APIchanges:
// 2018-01-28 - ea3672b7d6 - lavf 58.7.100 - avformat.h

View File

@@ -33,12 +33,7 @@ sc_demuxer_to_avcodec_id(uint32_t codec_id) {
case SC_CODEC_ID_H265:
return AV_CODEC_ID_HEVC;
case SC_CODEC_ID_AV1:
#ifdef SCRCPY_LAVC_HAS_AV1
return AV_CODEC_ID_AV1;
#else
LOGE("AV1 not supported by this FFmpeg version");
return AV_CODEC_ID_NONE;
#endif
case SC_CODEC_ID_OPUS:
return AV_CODEC_ID_OPUS;
case SC_CODEC_ID_AAC:

View File

@@ -52,7 +52,7 @@ const struct scrcpy_options scrcpy_options_default = {
.fullscreen = false,
.always_on_top = false,
.control = true,
.mirror = true,
.display = true,
.turn_screen_off = false,
.key_inject_mode = SC_KEY_INJECT_MODE_MIXED,
.window_borderless = false,
@@ -73,7 +73,6 @@ const struct scrcpy_options scrcpy_options_default = {
.cleanup = true,
.start_fps_counter = false,
.power_on = true,
.video = true,
.audio = true,
.require_audio = false,
.list_encoders = false,

View File

@@ -21,20 +21,8 @@ enum sc_record_format {
SC_RECORD_FORMAT_AUTO,
SC_RECORD_FORMAT_MP4,
SC_RECORD_FORMAT_MKV,
SC_RECORD_FORMAT_M4A,
SC_RECORD_FORMAT_MKA,
SC_RECORD_FORMAT_OPUS,
SC_RECORD_FORMAT_AAC,
};
static inline bool
sc_record_format_is_audio_only(enum sc_record_format fmt) {
return fmt == SC_RECORD_FORMAT_M4A
|| fmt == SC_RECORD_FORMAT_MKA
|| fmt == SC_RECORD_FORMAT_OPUS
|| fmt == SC_RECORD_FORMAT_AAC;
}
enum sc_codec {
SC_CODEC_H264,
SC_CODEC_H265,
@@ -147,7 +135,7 @@ struct scrcpy_options {
bool fullscreen;
bool always_on_top;
bool control;
bool mirror;
bool display;
bool turn_screen_off;
enum sc_key_inject_mode key_inject_mode;
bool window_borderless;
@@ -168,7 +156,6 @@ struct scrcpy_options {
bool cleanup;
bool start_fps_counter;
bool power_on;
bool video;
bool audio;
bool require_audio;
bool list_encoders;

View File

@@ -60,17 +60,9 @@ sc_recorder_queue_clear(struct sc_recorder_queue *queue) {
static const char *
sc_recorder_get_format_name(enum sc_record_format format) {
switch (format) {
case SC_RECORD_FORMAT_MP4:
case SC_RECORD_FORMAT_M4A:
case SC_RECORD_FORMAT_AAC:
return "mp4";
case SC_RECORD_FORMAT_MKV:
case SC_RECORD_FORMAT_MKA:
return "matroska";
case SC_RECORD_FORMAT_OPUS:
return "opus";
default:
return NULL;
case SC_RECORD_FORMAT_MP4: return "mp4";
case SC_RECORD_FORMAT_MKV: return "matroska";
default: return NULL;
}
}
@@ -160,7 +152,7 @@ sc_recorder_close_output_file(struct sc_recorder *recorder) {
static inline bool
sc_recorder_has_empty_queues(struct sc_recorder *recorder) {
if (recorder->video && sc_vecdeque_is_empty(&recorder->video_queue)) {
if (sc_vecdeque_is_empty(&recorder->video_queue)) {
// The video queue is empty
return true;
}
@@ -184,7 +176,7 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
sc_cond_wait(&recorder->stream_cond, &recorder->mutex);
}
if (recorder->video && sc_vecdeque_is_empty(&recorder->video_queue)) {
if (sc_vecdeque_is_empty(&recorder->video_queue)) {
assert(recorder->stopped);
// If the recorder is stopped, don't process anything if there are not
// at least video packets
@@ -192,11 +184,7 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
return false;
}
AVPacket *video_pkt = NULL;
if (!sc_vecdeque_is_empty(&recorder->video_queue)) {
assert(recorder->video);
video_pkt = sc_vecdeque_pop(&recorder->video_queue);
}
AVPacket *video_pkt = sc_vecdeque_pop(&recorder->video_queue);
AVPacket *audio_pkt = NULL;
if (!sc_vecdeque_is_empty(&recorder->audio_queue)) {
@@ -208,19 +196,17 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
int ret = false;
if (video_pkt) {
if (video_pkt->pts != AV_NOPTS_VALUE) {
LOGE("The first video packet is not a config packet");
goto end;
}
if (video_pkt->pts != AV_NOPTS_VALUE) {
LOGE("The first video packet is not a config packet");
goto end;
}
assert(recorder->video_stream_index >= 0);
AVStream *video_stream =
recorder->ctx->streams[recorder->video_stream_index];
bool ok = sc_recorder_set_extradata(video_stream, video_pkt);
if (!ok) {
goto end;
}
assert(recorder->video_stream_index >= 0);
AVStream *video_stream =
recorder->ctx->streams[recorder->video_stream_index];
bool ok = sc_recorder_set_extradata(video_stream, video_pkt);
if (!ok) {
goto end;
}
if (audio_pkt) {
@@ -232,13 +218,13 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
assert(recorder->audio_stream_index >= 0);
AVStream *audio_stream =
recorder->ctx->streams[recorder->audio_stream_index];
bool ok = sc_recorder_set_extradata(audio_stream, audio_pkt);
ok = sc_recorder_set_extradata(audio_stream, audio_pkt);
if (!ok) {
goto end;
}
}
bool ok = avformat_write_header(recorder->ctx, NULL) >= 0;
ok = avformat_write_header(recorder->ctx, NULL) >= 0;
if (!ok) {
LOGE("Failed to write header to %s", recorder->filename);
goto end;
@@ -247,9 +233,7 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
ret = true;
end:
if (video_pkt) {
av_packet_free(&video_pkt);
}
av_packet_free(&video_pkt);
if (audio_pkt) {
av_packet_free(&audio_pkt);
}
@@ -279,8 +263,7 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
sc_mutex_lock(&recorder->mutex);
while (!recorder->stopped) {
if (recorder->video && !video_pkt &&
!sc_vecdeque_is_empty(&recorder->video_queue)) {
if (!video_pkt && !sc_vecdeque_is_empty(&recorder->video_queue)) {
// A new packet may be assigned to video_pkt and be processed
break;
}
@@ -295,11 +278,6 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
// If stopped is set, continue to process the remaining events (to
// finish the recording) before actually stopping.
// If there is no video, then the video_queue will remain empty forever
// and video_pkt will always be NULL.
assert(recorder->video || (!video_pkt
&& sc_vecdeque_is_empty(&recorder->video_queue)));
// If there is no audio, then the audio_queue will remain empty forever
// and audio_pkt will always be NULL.
assert(recorder->audio || (!audio_pkt
@@ -341,9 +319,6 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
if (!recorder->audio) {
assert(video_pkt);
pts_origin = video_pkt->pts;
} else if (!recorder->video) {
assert(audio_pkt);
pts_origin = audio_pkt->pts;
} else if (video_pkt && audio_pkt) {
pts_origin = MIN(video_pkt->pts, audio_pkt->pts);
} else if (recorder->stopped) {
@@ -664,7 +639,7 @@ sc_recorder_audio_packet_sink_disable(struct sc_packet_sink *sink) {
bool
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
enum sc_record_format format, bool video, bool audio,
enum sc_record_format format, bool audio,
const struct sc_recorder_callbacks *cbs, void *cbs_userdata) {
recorder->filename = strdup(filename);
if (!recorder->filename) {
@@ -687,8 +662,6 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename,
goto error_queue_cond_destroy;
}
assert(video || audio);
recorder->video = video;
recorder->audio = audio;
sc_vecdeque_init(&recorder->video_queue);
@@ -707,15 +680,13 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename,
recorder->cbs = cbs;
recorder->cbs_userdata = cbs_userdata;
if (video) {
static const struct sc_packet_sink_ops video_ops = {
.open = sc_recorder_video_packet_sink_open,
.close = sc_recorder_video_packet_sink_close,
.push = sc_recorder_video_packet_sink_push,
};
static const struct sc_packet_sink_ops video_ops = {
.open = sc_recorder_video_packet_sink_open,
.close = sc_recorder_video_packet_sink_close,
.push = sc_recorder_video_packet_sink_push,
};
recorder->video_packet_sink.ops = &video_ops;
}
recorder->video_packet_sink.ops = &video_ops;
if (audio) {
static const struct sc_packet_sink_ops audio_ops = {

View File

@@ -27,7 +27,6 @@ struct sc_recorder {
* may access it without data races.
*/
bool audio;
bool video;
char *filename;
enum sc_record_format format;
@@ -60,7 +59,7 @@ struct sc_recorder_callbacks {
bool
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
enum sc_record_format format, bool video, bool audio,
enum sc_record_format format, bool audio,
const struct sc_recorder_callbacks *cbs, void *cbs_userdata);
bool

View File

@@ -137,7 +137,7 @@ sdl_set_hints(const char *render_driver) {
}
static void
sdl_configure(bool mirror, bool disable_screensaver) {
sdl_configure(bool display, bool disable_screensaver) {
#ifdef _WIN32
// Clean up properly on Ctrl+C on Windows
bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE);
@@ -146,7 +146,7 @@ sdl_configure(bool mirror, bool disable_screensaver) {
}
#endif // _WIN32
if (!mirror) {
if (!display) {
return;
}
@@ -345,7 +345,6 @@ scrcpy(struct scrcpy_options *options) {
.lock_video_orientation = options->lock_video_orientation,
.control = options->control,
.display_id = options->display_id,
.video = options->video,
.audio = options->audio,
.show_touches = options->show_touches,
.stay_awake = options->stay_awake,
@@ -386,11 +385,13 @@ scrcpy(struct scrcpy_options *options) {
goto end;
}
if (options->mirror) {
if (options->display) {
sdl_set_hints(options->render_driver);
}
// Initialize SDL video and audio in addition if mirroring is enabled
if (options->video && SDL_Init(SDL_INIT_VIDEO)) {
// Initialize SDL video in addition if display is enabled
if (options->display) {
if (SDL_Init(SDL_INIT_VIDEO)) {
LOGE("Could not initialize SDL video: %s", SDL_GetError());
goto end;
}
@@ -401,7 +402,7 @@ scrcpy(struct scrcpy_options *options) {
}
}
sdl_configure(options->mirror, options->disable_screensaver);
sdl_configure(options->display, options->disable_screensaver);
// Await for server without blocking Ctrl+C handling
bool connected;
@@ -427,8 +428,7 @@ scrcpy(struct scrcpy_options *options) {
struct sc_file_pusher *fp = NULL;
assert(!options->control || options->mirror); // control implies mirror
if (options->control) {
if (options->display && options->control) {
if (!sc_file_pusher_init(&s->file_pusher, serial,
options->push_target)) {
goto end;
@@ -437,13 +437,11 @@ scrcpy(struct scrcpy_options *options) {
file_pusher_initialized = true;
}
if (options->video) {
static const struct sc_demuxer_callbacks video_demuxer_cbs = {
.on_ended = sc_video_demuxer_on_ended,
};
sc_demuxer_init(&s->video_demuxer, "video", s->server.video_socket,
&video_demuxer_cbs, NULL);
}
static const struct sc_demuxer_callbacks video_demuxer_cbs = {
.on_ended = sc_video_demuxer_on_ended,
};
sc_demuxer_init(&s->video_demuxer, "video", s->server.video_socket,
&video_demuxer_cbs, NULL);
if (options->audio) {
static const struct sc_demuxer_callbacks audio_demuxer_cbs = {
@@ -453,8 +451,8 @@ scrcpy(struct scrcpy_options *options) {
&audio_demuxer_cbs, options);
}
bool needs_video_decoder = options->mirror && options->video;
bool needs_audio_decoder = options->mirror && options->audio;
bool needs_video_decoder = options->display;
bool needs_audio_decoder = options->audio && options->display;
#ifdef HAVE_V4L2
needs_video_decoder |= !!options->v4l2_device;
#endif
@@ -474,8 +472,8 @@ scrcpy(struct scrcpy_options *options) {
.on_ended = sc_recorder_on_ended,
};
if (!sc_recorder_init(&s->recorder, options->record_filename,
options->record_format, options->video,
options->audio, &recorder_cbs, NULL)) {
options->record_format, options->audio,
&recorder_cbs, NULL)) {
goto end;
}
recorder_initialized = true;
@@ -485,10 +483,8 @@ scrcpy(struct scrcpy_options *options) {
}
recorder_started = true;
if (options->video) {
sc_packet_source_add_sink(&s->video_demuxer.packet_source,
&s->recorder.video_packet_sink);
}
sc_packet_source_add_sink(&s->video_demuxer.packet_source,
&s->recorder.video_packet_sink);
if (options->audio) {
sc_packet_source_add_sink(&s->audio_demuxer.packet_source,
&s->recorder.audio_packet_sink);
@@ -634,23 +630,12 @@ aoa_hid_end:
}
controller_started = true;
controller = &s->controller;
if (options->turn_screen_off) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = SC_SCREEN_POWER_MODE_OFF;
if (!sc_controller_push_msg(&s->controller, &msg)) {
LOGW("Could not request 'set screen power mode'");
}
}
}
// There is a controller if and only if control is enabled
assert(options->control == !!controller);
if (options->mirror) {
if (options->display) {
const char *window_title =
options->window_title ? options->window_title : info->device_name;
@@ -676,6 +661,11 @@ aoa_hid_end:
.start_fps_counter = options->start_fps_counter,
};
if (!sc_screen_init(&s->screen, &screen_params)) {
goto end;
}
screen_initialized = true;
struct sc_frame_source *src = &s->video_decoder.frame_source;
if (options->display_buffer) {
sc_delay_buffer_init(&s->display_buffer, options->display_buffer,
@@ -684,14 +674,7 @@ aoa_hid_end:
src = &s->display_buffer.frame_source;
}
if (options->video) {
if (!sc_screen_init(&s->screen, &screen_params)) {
goto end;
}
screen_initialized = true;
sc_frame_source_add_sink(src, &s->screen.frame_sink);
}
sc_frame_source_add_sink(src, &s->screen.frame_sink);
if (options->audio) {
sc_audio_player_init(&s->audio_player, options->audio_buffer,
@@ -720,15 +703,12 @@ aoa_hid_end:
}
#endif
// Now that the header values have been consumed, the socket(s) will
// receive the stream(s). Start the demuxer(s).
if (options->video) {
if (!sc_demuxer_start(&s->video_demuxer)) {
goto end;
}
video_demuxer_started = true;
// now we consumed the header values, the socket receives the video stream
// start the video demuxer
if (!sc_demuxer_start(&s->video_demuxer)) {
goto end;
}
video_demuxer_started = true;
if (options->audio) {
if (!sc_demuxer_start(&s->audio_demuxer)) {
@@ -737,6 +717,18 @@ aoa_hid_end:
audio_demuxer_started = true;
}
// If the device screen is to be turned off, send the control message after
// everything is set up
if (options->control && options->turn_screen_off) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = SC_SCREEN_POWER_MODE_OFF;
if (!sc_controller_push_msg(&s->controller, &msg)) {
LOGW("Could not request 'set screen power mode'");
}
}
ret = event_loop(s);
LOGD("quit...");

View File

@@ -226,16 +226,12 @@ execute_server(struct sc_server *server,
ADD_PARAM("scid=%08x", params->scid);
ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level));
if (!params->video) {
ADD_PARAM("video=false");
}
if (params->video_bit_rate) {
ADD_PARAM("video_bit_rate=%" PRIu32, params->video_bit_rate);
}
if (!params->audio) {
ADD_PARAM("audio=false");
}
if (params->audio_bit_rate) {
} else if (params->audio_bit_rate) {
ADD_PARAM("audio_bit_rate=%" PRIu32, params->audio_bit_rate);
}
if (params->video_codec != SC_CODEC_H264) {
@@ -467,7 +463,6 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
const char *serial = server->serial;
assert(serial);
bool video = server->params.video;
bool audio = server->params.audio;
bool control = server->params.control;
@@ -475,12 +470,9 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
sc_socket audio_socket = SC_SOCKET_NONE;
sc_socket control_socket = SC_SOCKET_NONE;
if (!tunnel->forward) {
if (video) {
video_socket =
net_accept_intr(&server->intr, tunnel->server_socket);
if (video_socket == SC_SOCKET_NONE) {
goto fail;
}
video_socket = net_accept_intr(&server->intr, tunnel->server_socket);
if (video_socket == SC_SOCKET_NONE) {
goto fail;
}
if (audio) {
@@ -511,45 +503,35 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
unsigned attempts = 100;
sc_tick delay = SC_TICK_FROM_MS(100);
sc_socket first_socket = connect_to_server(server, attempts, delay,
tunnel_host, tunnel_port);
if (first_socket == SC_SOCKET_NONE) {
video_socket = connect_to_server(server, attempts, delay, tunnel_host,
tunnel_port);
if (video_socket == SC_SOCKET_NONE) {
goto fail;
}
if (video) {
video_socket = first_socket;
}
if (audio) {
if (!video) {
audio_socket = first_socket;
} else {
audio_socket = net_socket();
if (audio_socket == SC_SOCKET_NONE) {
goto fail;
}
bool ok = net_connect_intr(&server->intr, audio_socket, tunnel_host,
tunnel_port);
if (!ok) {
goto fail;
}
audio_socket = net_socket();
if (audio_socket == SC_SOCKET_NONE) {
goto fail;
}
bool ok = net_connect_intr(&server->intr, audio_socket, tunnel_host,
tunnel_port);
if (!ok) {
goto fail;
}
}
if (control) {
if (!video && !audio) {
control_socket = first_socket;
} else {
control_socket = net_socket();
if (control_socket == SC_SOCKET_NONE) {
goto fail;
}
bool ok = net_connect_intr(&server->intr, control_socket,
tunnel_host, tunnel_port);
if (!ok) {
goto fail;
}
// we know that the device is listening, we don't need several
// attempts
control_socket = net_socket();
if (control_socket == SC_SOCKET_NONE) {
goto fail;
}
bool ok = net_connect_intr(&server->intr, control_socket,
tunnel_host, tunnel_port);
if (!ok) {
goto fail;
}
}
}
@@ -558,17 +540,13 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
sc_adb_tunnel_close(tunnel, &server->intr, serial,
server->device_socket_name);
sc_socket first_socket = video ? video_socket
: audio ? audio_socket
: control_socket;
// The sockets will be closed on stop if device_read_info() fails
bool ok = device_read_info(&server->intr, first_socket, info);
bool ok = device_read_info(&server->intr, video_socket, info);
if (!ok) {
goto fail;
}
assert(!video || video_socket != SC_SOCKET_NONE);
assert(video_socket != SC_SOCKET_NONE);
assert(!audio || audio_socket != SC_SOCKET_NONE);
assert(!control || control_socket != SC_SOCKET_NONE);
@@ -952,11 +930,8 @@ run_server(void *data) {
sc_mutex_unlock(&server->mutex);
// Interrupt sockets to wake up socket blocking calls on the server
if (server->video_socket != SC_SOCKET_NONE) {
// There is no video_socket if --no-video is set
net_interrupt(server->video_socket);
}
assert(server->video_socket != SC_SOCKET_NONE);
net_interrupt(server->video_socket);
if (server->audio_socket != SC_SOCKET_NONE) {
// There is no audio_socket if --no-audio is set

View File

@@ -41,7 +41,6 @@ struct sc_server_params {
int8_t lock_video_orientation;
bool control;
uint32_t display_id;
bool video;
bool audio;
bool show_touches;
bool stay_awake;

View File

@@ -53,7 +53,7 @@ static void test_options(void) {
"--max-size", "1024",
"--lock-video-orientation=2", // optional arguments require '='
// "--no-control" is not compatible with "--turn-screen-off"
// "--no-mirror" is not compatible with "--fulscreen"
// "--no-display" is not compatible with "--fulscreen"
"--port", "1234:1236",
"--push-target", "/sdcard/Movies",
"--record", "file",
@@ -108,8 +108,8 @@ static void test_options2(void) {
char *argv[] = {
"scrcpy",
"--no-control",
"--no-mirror",
"--record", "file.mp4", // cannot enable --no-mirror without recording
"--no-display",
"--record", "file.mp4", // cannot enable --no-display without recording
};
bool ok = scrcpy_parse_args(&args, ARRAY_LEN(argv), argv);
@@ -117,7 +117,7 @@ static void test_options2(void) {
const struct scrcpy_options *opts = &args.opts;
assert(!opts->control);
assert(!opts->mirror);
assert(!opts->display);
assert(!strcmp(opts->record_filename, "file.mp4"));
assert(opts->record_format == SC_RECORD_FORMAT_MP4);
}

View File

@@ -16,6 +16,6 @@ cpu = 'i686'
endian = 'little'
[properties]
prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-3/win32'
prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-2/win32'
prebuilt_sdl2 = 'SDL2-2.26.4/i686-w64-mingw32'
prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32'

View File

@@ -16,6 +16,6 @@ cpu = 'x86_64'
endian = 'little'
[properties]
prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-3/win64'
prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-2/win64'
prebuilt_sdl2 = 'SDL2-2.26.4/x86_64-w64-mingw32'
prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64'

View File

@@ -24,21 +24,6 @@ To disable audio:
scrcpy --no-audio
```
## Audio only
To play audio only, disable the video:
```
scrcpy --no-video
```
Without video, the audio latency is typically not criticial, so it might be
interesting to add [buffering](#buffering) to minimize glitches:
```
scrcpy --no-video --audio-buffer=200
```
## Codec
The audio codec can be selected. The possible values are `opus` (default), `aac`

View File

@@ -13,18 +13,12 @@ To record only the video:
scrcpy --no-audio --record=file.mp4
```
To record only the audio:
```bash
scrcpy --no-video --record=file.opus
scrcpy --no-video --audio-codec=aac --record-file=file.aac
# .m4a/.mp4 and .mka/.mkv are also supported for both opus and aac
```
_It is currently not possible to record only the audio._
To disable mirroring while recording:
```bash
scrcpy --no-mirror --record=file.mp4
scrcpy --no-display --record=file.mp4
scrcpy -Nr file.mkv
# interrupt recording with Ctrl+C
```

View File

@@ -35,7 +35,7 @@ To start `scrcpy` using a v4l2 sink:
```bash
scrcpy --v4l2-sink=/dev/videoN
scrcpy --v4l2-sink=/dev/videoN --no-mirror # disable mirroring window
scrcpy --v4l2-sink=/dev/videoN --no-display # disable mirroring window
```
(replace `N` with the device ID, check with `ls /dev/video*`)

View File

@@ -159,27 +159,17 @@ scrcpy --display-buffer=50 --v4l2-buffer=300
```
## No mirror
## No display
It is possible to capture an Android device without displaying a mirroring
window. This option is available if either [recording](recording.md) or
[v4l2](#video4linux) is enabled:
```bash
scrcpy --v4l2-sink=/dev/video2 --no-mirror
scrcpy --record=file.mkv --no-mirror
scrcpy --v4l2-sink=/dev/video2 --no-display
scrcpy --record=file.mkv --no-display
```
## No video
To disable video forwarding completely, so that only audio is forwarded:
```
scrcpy --no-video
```
## Video4Linux
See the dedicated [Video4Linux](v4l2.md) page.

View File

@@ -94,11 +94,11 @@ dist-win32: build-server build-win32
cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)"
cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)"
cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win32/bin/zlib1.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win32/bin/zlib1.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.1/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
@@ -113,11 +113,11 @@ dist-win64: build-server build-win64
cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)"
cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-3/win64/bin/zlib1.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-2/win64/bin/zlib1.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.1/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"

View File

@@ -1,16 +1,7 @@
package com.genymobile.scrcpy;
public interface AsyncProcessor {
interface TerminationListener {
/**
* Notify processor termination
*
* @param fatalError {@code true} if this must cause the termination of the whole scrcpy-server.
*/
void onTerminated(boolean fatalError);
}
void start(TerminationListener listener);
void start();
void stop();
void join() throws InterruptedException;
}

View File

@@ -114,29 +114,21 @@ public final class AudioEncoder implements AsyncProcessor {
}
}
@Override
public void start(TerminationListener listener) {
public void start() {
thread = new Thread(() -> {
boolean fatalError = false;
try {
encode();
} catch (ConfigurationException e) {
// Do not print stack trace, a user-friendly error-message has already been logged
fatalError = true;
} catch (AudioCaptureForegroundException e) {
} catch (ConfigurationException | AudioCaptureForegroundException e) {
// Do not print stack trace, a user-friendly error-message has already been logged
} catch (IOException e) {
Ln.e("Audio encoding error", e);
fatalError = true;
} finally {
Ln.d("Audio encoder stopped");
listener.onTerminated(fatalError);
}
});
thread.start();
}
@Override
public void stop() {
if (thread != null) {
// Just wake up the blocking wait from the thread, so that it properly releases all its resources and terminates
@@ -144,7 +136,6 @@ public final class AudioEncoder implements AsyncProcessor {
}
}
@Override
public void join() throws InterruptedException {
if (thread != null) {
thread.join();

View File

@@ -53,33 +53,27 @@ public final class AudioRawRecorder implements AsyncProcessor {
}
}
@Override
public void start(TerminationListener listener) {
public void start() {
thread = new Thread(() -> {
boolean fatalError = false;
try {
record();
} catch (AudioCaptureForegroundException e) {
// Do not print stack trace, a user-friendly error-message has already been logged
} catch (IOException e) {
Ln.e("Audio recording error", e);
fatalError = true;
} finally {
Ln.d("Audio recorder stopped");
listener.onTerminated(fatalError);
}
});
thread.start();
}
@Override
public void stop() {
if (thread != null) {
thread.interrupt();
}
}
@Override
public void join() throws InterruptedException {
if (thread != null) {
thread.join();

View File

@@ -84,8 +84,7 @@ public class Controller implements AsyncProcessor {
}
}
@Override
public void start(TerminationListener listener) {
public void start() {
thread = new Thread(() -> {
try {
control();
@@ -93,14 +92,12 @@ public class Controller implements AsyncProcessor {
// this is expected on close
} finally {
Ln.d("Controller stopped");
listener.onTerminated(true);
}
});
thread.start();
sender.start();
}
@Override
public void stop() {
if (thread != null) {
thread.interrupt();
@@ -108,7 +105,6 @@ public class Controller implements AsyncProcessor {
sender.stop();
}
@Override
public void join() throws InterruptedException {
if (thread != null) {
thread.join();

View File

@@ -41,7 +41,7 @@ public final class DesktopConnection implements Closeable {
controlInputStream = null;
controlOutputStream = null;
}
videoFd = videoSocket != null ? videoSocket.getFileDescriptor() : null;
videoFd = videoSocket.getFileDescriptor();
audioFd = audioSocket != null ? audioSocket.getFileDescriptor() : null;
}
@@ -60,43 +60,29 @@ public final class DesktopConnection implements Closeable {
return SOCKET_NAME_PREFIX + String.format("_%08x", scid);
}
public static DesktopConnection open(int scid, boolean tunnelForward, boolean video, boolean audio, boolean control, boolean sendDummyByte)
throws IOException {
public static DesktopConnection open(int scid, boolean tunnelForward, boolean audio, boolean control, boolean sendDummyByte) throws IOException {
String socketName = getSocketName(scid);
LocalSocket firstSocket = null;
LocalSocket videoSocket = null;
LocalSocket audioSocket = null;
LocalSocket controlSocket = null;
try {
if (tunnelForward) {
try (LocalServerSocket localServerSocket = new LocalServerSocket(socketName)) {
if (video) {
videoSocket = localServerSocket.accept();
firstSocket = videoSocket;
videoSocket = localServerSocket.accept();
if (sendDummyByte) {
// send one byte so the client may read() to detect a connection error
videoSocket.getOutputStream().write(0);
}
if (audio) {
audioSocket = localServerSocket.accept();
if (firstSocket == null) {
firstSocket = audioSocket;
}
}
if (control) {
controlSocket = localServerSocket.accept();
if (firstSocket == null) {
firstSocket = controlSocket;
}
}
if (sendDummyByte) {
// send one byte so the client may read() to detect a connection error
firstSocket.getOutputStream().write(0);
}
}
} else {
if (video) {
videoSocket = connect(socketName);
}
videoSocket = connect(socketName);
if (audio) {
audioSocket = connect(socketName);
}
@@ -120,22 +106,10 @@ public final class DesktopConnection implements Closeable {
return new DesktopConnection(videoSocket, audioSocket, controlSocket);
}
private LocalSocket getFirstSocket() {
if (videoSocket != null) {
return videoSocket;
}
if (audioSocket != null) {
return audioSocket;
}
return controlSocket;
}
public void close() throws IOException {
if (videoSocket != null) {
videoSocket.shutdownInput();
videoSocket.shutdownOutput();
videoSocket.close();
}
videoSocket.shutdownInput();
videoSocket.shutdownOutput();
videoSocket.close();
if (audioSocket != null) {
audioSocket.shutdownInput();
audioSocket.shutdownOutput();
@@ -156,8 +130,7 @@ public final class DesktopConnection implements Closeable {
System.arraycopy(deviceNameBytes, 0, buffer, 0, len);
// byte[] are always 0-initialized in java, no need to set '\0' explicitly
FileDescriptor fd = getFirstSocket().getFileDescriptor();
IO.writeFully(fd, buffer, 0, buffer.length);
IO.writeFully(videoFd, buffer, 0, buffer.length);
}
public FileDescriptor getVideoFd() {

View File

@@ -9,7 +9,6 @@ public class Options {
private Ln.Level logLevel = Ln.Level.DEBUG;
private int scid = -1; // 31-bit non-negative value, or -1
private boolean video = true;
private boolean audio = true;
private int maxSize;
private VideoCodec videoCodec = VideoCodec.H264;
@@ -52,10 +51,6 @@ public class Options {
return scid;
}
public boolean getVideo() {
return video;
}
public boolean getAudio() {
return audio;
}
@@ -205,9 +200,6 @@ public class Options {
case "log_level":
options.logLevel = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH));
break;
case "video":
options.video = Boolean.parseBoolean(value);
break;
case "audio":
options.audio = Boolean.parseBoolean(value);
break;

View File

@@ -16,7 +16,7 @@ import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public class ScreenEncoder implements Device.RotationListener, AsyncProcessor {
public class ScreenEncoder implements Device.RotationListener {
private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds
private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms
@@ -39,9 +39,6 @@ public class ScreenEncoder implements Device.RotationListener, AsyncProcessor {
private boolean firstFrameSent;
private int consecutiveErrors;
private Thread thread;
private final AtomicBoolean stopped = new AtomicBoolean();
public ScreenEncoder(Device device, Streamer streamer, int videoBitRate, int maxFps, List<CodecOption> codecOptions, String encoderName,
boolean downsizeOnError) {
this.device = device;
@@ -58,11 +55,11 @@ public class ScreenEncoder implements Device.RotationListener, AsyncProcessor {
rotationChanged.set(true);
}
private boolean consumeRotationChange() {
public boolean consumeRotationChange() {
return rotationChanged.getAndSet(false);
}
private void streamScreen() throws IOException, ConfigurationException {
public void streamScreen() throws IOException, ConfigurationException {
Codec codec = streamer.getCodec();
MediaCodec mediaCodec = createMediaCodec(codec, encoderName);
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
@@ -166,14 +163,9 @@ public class ScreenEncoder implements Device.RotationListener, AsyncProcessor {
private boolean encode(MediaCodec codec, Streamer streamer) throws IOException {
boolean eof = false;
boolean alive = true;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while (!consumeRotationChange() && !eof) {
if (stopped.get()) {
alive = false;
break;
}
int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
try {
if (consumeRotationChange()) {
@@ -201,7 +193,7 @@ public class ScreenEncoder implements Device.RotationListener, AsyncProcessor {
}
}
return !eof && alive;
return !eof;
}
private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException {
@@ -275,38 +267,4 @@ public class ScreenEncoder implements Device.RotationListener, AsyncProcessor {
SurfaceControl.closeTransaction();
}
}
@Override
public void start(TerminationListener listener) {
thread = new Thread(() -> {
try {
streamScreen();
} catch (ConfigurationException e) {
// Do not print stack trace, a user-friendly error-message has already been logged
} catch (IOException e) {
// Broken pipe is expected on close, because the socket is closed by the client
if (!IO.isBrokenPipe(e)) {
Ln.e("Video encoding error", e);
}
} finally {
Ln.d("Screen streaming stopped");
listener.onTerminated(true);
}
});
thread.start();
}
@Override
public void stop() {
if (thread != null) {
stopped.set(true);
}
}
@Override
public void join() throws InterruptedException {
if (thread != null) {
thread.join();
}
}
}

View File

@@ -9,35 +9,6 @@ import java.util.List;
public final class Server {
private static class Completion {
private int running;
private boolean fatalError;
Completion(int running) {
this.running = running;
}
synchronized void addCompleted(boolean fatalError) {
--running;
if (fatalError) {
this.fatalError = true;
}
if (running == 0 || this.fatalError) {
notify();
}
}
synchronized void await() {
try {
while (running > 0 && !fatalError) {
wait();
}
} catch (InterruptedException e) {
// ignore
}
}
}
private Server() {
// not instantiable
}
@@ -95,7 +66,6 @@ public final class Server {
int scid = options.getScid();
boolean tunnelForward = options.isTunnelForward();
boolean control = options.getControl();
boolean video = options.getVideo();
boolean audio = options.getAudio();
boolean sendDummyByte = options.getSendDummyByte();
@@ -122,8 +92,7 @@ public final class Server {
List<AsyncProcessor> asyncProcessors = new ArrayList<>();
DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, video, audio, control, sendDummyByte);
try {
try (DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, audio, control, sendDummyByte)) {
if (options.getSendDeviceMeta()) {
connection.sendDeviceMeta(Device.getDeviceName());
}
@@ -148,23 +117,26 @@ public final class Server {
asyncProcessors.add(audioRecorder);
}
if (video) {
Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(),
options.getSendFrameMeta());
ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(),
options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError());
asyncProcessors.add(screenEncoder);
}
Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(),
options.getSendFrameMeta());
ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(),
options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError());
Completion completion = new Completion(asyncProcessors.size());
for (AsyncProcessor asyncProcessor : asyncProcessors) {
asyncProcessor.start((fatalError) -> {
completion.addCompleted(fatalError);
});
asyncProcessor.start();
}
completion.await();
try {
// synchronous
screenEncoder.streamScreen();
} catch (IOException e) {
// Broken pipe is expected on close, because the socket is closed by the client
if (!IO.isBrokenPipe(e)) {
Ln.e("Video encoding error", e);
}
}
} finally {
Ln.d("Screen streaming stopped");
initThread.interrupt();
for (AsyncProcessor asyncProcessor : asyncProcessors) {
asyncProcessor.stop();
@@ -178,8 +150,6 @@ public final class Server {
} catch (InterruptedException e) {
// ignore
}
connection.close();
}
}