Compare commits

..

1 Commits

Author SHA1 Message Date
Simon Chan
4ab1e60529 Create AudioRecord by reflection as a fallback
Some devices (Vivo phones) fail to create an AudioRecord from an
AudioRecord.Builder (which throw a NullPointerException).

In that case, create an AudioRecord instance directly by reflection.

The AOSP version of AudioRecord constructor code can be found at:
 - Android 11 (R):
   <https://cs.android.com/android/platform/superproject/+/android-11.0.0_r1:frameworks/base/media/java/android/media/AudioRecord.java;l=335;drc=64ed2ec38a511bbbd048985fe413268335e072f8>
 - Android 12 (S):
   <https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/media/java/android/media/AudioRecord.java;l=388;drc=2eebf929650e0d320a21f0d13677a27d7ab278e9>
 - Android 13 (T, functionally identical to Android 12):
   <https://cs.android.com/android/platform/superproject/+/android-13.0.0_r1:frameworks/base/media/java/android/media/AudioRecord.java;l=382;drc=ed242da52f975a1dd18671afb346b18853d729f2>
 - Android 14 (U): Not released, but expected to change

PR #3862 <https://github.com/Genymobile/scrcpy/pull/3862>
Fixes #3805 <https://github.com/Genymobile/scrcpy/issues/3805>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-05-25 15:50:52 +02:00
16 changed files with 113 additions and 245 deletions

View File

@@ -29,17 +29,15 @@ _scrcpy() {
-M --hid-mouse -M --hid-mouse
-m --max-size= -m --max-size=
--no-audio --no-audio
--no-audio-playback
--no-cleanup --no-cleanup
--no-clipboard-autosync --no-clipboard-autosync
--no-downsize-on-error --no-downsize-on-error
-n --no-control -n --no-control
-N --no-playback -N --no-mirror
--no-key-repeat --no-key-repeat
--no-mipmaps --no-mipmaps
--no-power-on --no-power-on
--no-video --no-video
--no-video-playback
--otg --otg
-p --port= -p --port=
--power-off-on-close --power-off-on-close

View File

@@ -35,17 +35,15 @@ arguments=(
{-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]' {-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]'
{-m,--max-size=}'[Limit both the width and height of the video to value]' {-m,--max-size=}'[Limit both the width and height of the video to value]'
'--no-audio[Disable audio forwarding]' '--no-audio[Disable audio forwarding]'
'--no-audio-playback[Disable audio playback]'
'--no-cleanup[Disable device cleanup actions on exit]' '--no-cleanup[Disable device cleanup actions on exit]'
'--no-clipboard-autosync[Disable automatic clipboard synchronization]' '--no-clipboard-autosync[Disable automatic clipboard synchronization]'
'--no-downsize-on-error[Disable lowering definition on MediaCodec error]' '--no-downsize-on-error[Disable lowering definition on MediaCodec error]'
{-n,--no-control}'[Disable device control \(mirror the device in read only\)]' {-n,--no-control}'[Disable device control \(mirror the device in read only\)]'
{-N,--no-playback}'[Disable video and audio playback]' {-N,--no-mirror}'[Do not mirror device \(only when recording or V4L2 sink is enabled\)]'
'--no-key-repeat[Do not forward repeated key events when a key is held down]' '--no-key-repeat[Do not forward repeated key events when a key is held down]'
'--no-mipmaps[Disable the generation of mipmaps]' '--no-mipmaps[Disable the generation of mipmaps]'
'--no-power-on[Do not power on the device on start]' '--no-power-on[Do not power on the device on start]'
'--no-video[Disable video forwarding]' '--no-video[Disable video forwarding]'
'--no-video-playback[Disable video playback]'
'--otg[Run in OTG mode \(simulating physical keyboard and mouse\)]' '--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]' {-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]' '--power-off-on-close[Turn the device screen off when closing scrcpy]'

View File

@@ -187,10 +187,6 @@ Also see \fB\-\-hid\-keyboard\fR.
.B \-\-no\-audio .B \-\-no\-audio
Disable audio forwarding. Disable audio forwarding.
.TP
.B \-\-no\-audio\-playback
Disable audio playback on the computer.
.TP .TP
.B \-\-no\-cleanup .B \-\-no\-cleanup
By default, scrcpy removes the server binary from the device and restores the device state (show touches, stay awake and power mode) on exit. By default, scrcpy removes the server binary from the device and restores the device state (show touches, stay awake and power mode) on exit.
@@ -214,8 +210,8 @@ This option disables this behavior.
Disable device control (mirror the device in read\-only). Disable device control (mirror the device in read\-only).
.TP .TP
.B \-N, \-\-no\-playback .B \-N, \-\-no\-mirror
Disable video and audio playback on the computer (equivalent to --no-video-playback --no-audio-playback). Do not mirror device video or audio on the computer (only when recording or V4L2 sink is enabled).
.TP .TP
.B \-\-no\-key\-repeat .B \-\-no\-key\-repeat
@@ -233,10 +229,6 @@ Do not power on the device on start.
.B \-\-no\-video .B \-\-no\-video
Disable video forwarding. Disable video forwarding.
.TP
.B \-\-no\-video\-playback
Disable video playback on the computer.
.TP .TP
.B \-\-otg .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. 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

@@ -74,8 +74,6 @@ enum {
OPT_AUDIO_OUTPUT_BUFFER, OPT_AUDIO_OUTPUT_BUFFER,
OPT_NO_DISPLAY, OPT_NO_DISPLAY,
OPT_NO_VIDEO, OPT_NO_VIDEO,
OPT_NO_AUDIO_PLAYBACK,
OPT_NO_VIDEO_PLAYBACK,
}; };
struct sc_option { struct sc_option {
@@ -353,11 +351,6 @@ static const struct sc_option options[] = {
.longopt = "no-audio", .longopt = "no-audio",
.text = "Disable audio forwarding.", .text = "Disable audio forwarding.",
}, },
{
.longopt_id = OPT_NO_AUDIO_PLAYBACK,
.longopt = "no-audio-playback",
.text = "Disable audio playback on the computer.",
},
{ {
.longopt_id = OPT_NO_CLEANUP, .longopt_id = OPT_NO_CLEANUP,
.longopt = "no-cleanup", .longopt = "no-cleanup",
@@ -389,9 +382,9 @@ static const struct sc_option options[] = {
}, },
{ {
.shortopt = 'N', .shortopt = 'N',
.longopt = "no-playback", .longopt = "no-mirror",
.text = "Disable video and audio playback on the computer (equivalent " .text = "Do not mirror device video or audio on the computer (only "
"to --no-video-playback --no-audio-playback).", "when recording or V4L2 sink is enabled).",
}, },
{ {
// deprecated // deprecated
@@ -420,11 +413,6 @@ static const struct sc_option options[] = {
.longopt = "no-video", .longopt = "no-video",
.text = "Disable video forwarding.", .text = "Disable video forwarding.",
}, },
{
.longopt_id = OPT_NO_VIDEO_PLAYBACK,
.longopt = "no-video-playback",
.text = "Disable video playback on the computer.",
},
{ {
.longopt_id = OPT_OTG, .longopt_id = OPT_OTG,
.longopt = "otg", .longopt = "otg",
@@ -1683,17 +1671,10 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->control = false; opts->control = false;
break; break;
case OPT_NO_DISPLAY: case OPT_NO_DISPLAY:
LOGW("--no-display is deprecated, use --no-playback instead."); LOGW("--no-display is deprecated, use --no-mirror instead.");
// fall through // fall through
case 'N': case 'N':
opts->video_playback = false; opts->mirror = false;
opts->audio_playback = false;
break;
case OPT_NO_VIDEO_PLAYBACK:
opts->video_playback = false;
break;
case OPT_NO_AUDIO_PLAYBACK:
opts->audio_playback = false;
break; break;
case 'p': case 'p':
if (!parse_port_range(optarg, &opts->port_range)) { if (!parse_port_range(optarg, &opts->port_range)) {
@@ -1891,8 +1872,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
} }
break; break;
#else #else
LOGE("V4L2 (--v4l2-buffer) is disabled (or unsupported on this " LOGE("V4L2 (--v4l2-buffer) is only available on Linux.");
"platform).");
return false; return false;
#endif #endif
case OPT_LIST_ENCODERS: case OPT_LIST_ENCODERS:
@@ -1943,52 +1923,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false; return false;
} }
bool otg = false;
bool v4l2 = false;
#ifdef HAVE_USB
otg = opts->otg;
#endif
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
v4l2 = !!opts->v4l2_device; if (!opts->mirror && !opts->record_filename && !opts->v4l2_device) {
#endif LOGE("-N/--no-mirror requires either screen recording (-r/--record)"
" or sink to v4l2loopback device (--v4l2-sink)");
if (!opts->video) {
opts->video_playback = false;
}
if (!opts->audio) {
opts->audio_playback = false;
}
if (!opts->video_playback && !otg) {
// If video playback is disabled and OTG are disabled, then there is
// no way to control the device.
opts->control = false;
}
if (opts->video && !opts->video_playback && !opts->record_filename
&& !v4l2) {
LOGI("No video playback, no recording, no V4L2 sink: video disabled");
opts->video = false;
}
if (opts->audio && !opts->audio_playback && !opts->record_filename) {
LOGI("No audio playback, no recording: audio disabled");
opts->audio = false;
}
if (!opts->video && !opts->audio && !otg) {
LOGE("No video, no audio, no OTG: nothing to do");
return false; return false;
} }
if (!opts->video && !otg) { if (opts->v4l2_device) {
// If video is disabled, then scrcpy must exit on audio failure.
opts->require_audio = true;
}
#ifdef HAVE_V4L2
if (v4l2) {
if (opts->lock_video_orientation == if (opts->lock_video_orientation ==
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) { SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
LOGI("Video orientation is locked for v4l2 sink. " LOGI("Video orientation is locked for v4l2 sink. "
@@ -2006,8 +1948,18 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
LOGE("V4L2 buffer value without V4L2 sink\n"); LOGE("V4L2 buffer value without V4L2 sink\n");
return false; return false;
} }
#else
if (!opts->mirror && !opts->record_filename) {
LOGE("-N/--no-mirror requires screen recording (-r/--record)");
return false;
}
#endif #endif
if (opts->audio && !opts->mirror && !opts->record_filename) {
LOGI("No mirror and no recording: audio disabled");
opts->audio = false;
}
if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) { if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) {
LOGI("Tunnel host/port is set, " LOGI("Tunnel host/port is set, "
"--force-adb-forward automatically enabled."); "--force-adb-forward automatically enabled.");
@@ -2087,9 +2039,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
} }
} }
#ifdef HAVE_USB
# ifdef _WIN32 # ifdef _WIN32
if (!otg && (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID if (!opts->otg && (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID
|| opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID)) { || opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID)) {
LOGE("On Windows, it is not possible to open a USB device already open " LOGE("On Windows, it is not possible to open a USB device already open "
"by another process (like adb)."); "by another process (like adb).");
LOGE("Therefore, -K/--hid-keyboard and -M/--hid-mouse may only work in " LOGE("Therefore, -K/--hid-keyboard and -M/--hid-mouse may only work in "
@@ -2098,7 +2052,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
} }
# endif # endif
if (otg) { if (opts->otg) {
// OTG mode is compatible with only very few options. // OTG mode is compatible with only very few options.
// Only report obvious errors. // Only report obvious errors.
if (opts->record_filename) { if (opts->record_filename) {
@@ -2125,10 +2079,28 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
LOGE("OTG mode: could not select display"); LOGE("OTG mode: could not select display");
return false; return false;
} }
if (v4l2) { # ifdef HAVE_V4L2
if (opts->v4l2_device) {
LOGE("OTG mode: could not sink to V4L2 device"); LOGE("OTG mode: could not sink to V4L2 device");
return false; return false;
} }
# endif
}
#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; return true;

View File

@@ -11,6 +11,9 @@ const struct scrcpy_options scrcpy_options_default = {
.audio_codec_options = NULL, .audio_codec_options = NULL,
.video_encoder = NULL, .video_encoder = NULL,
.audio_encoder = NULL, .audio_encoder = NULL,
#ifdef HAVE_V4L2
.v4l2_device = NULL,
#endif
.log_level = SC_LOG_LEVEL_INFO, .log_level = SC_LOG_LEVEL_INFO,
.video_codec = SC_CODEC_H264, .video_codec = SC_CODEC_H264,
.audio_codec = SC_CODEC_OPUS, .audio_codec = SC_CODEC_OPUS,
@@ -39,12 +42,9 @@ const struct scrcpy_options scrcpy_options_default = {
.window_height = 0, .window_height = 0,
.display_id = 0, .display_id = 0,
.display_buffer = 0, .display_buffer = 0,
.v4l2_buffer = 0,
.audio_buffer = SC_TICK_FROM_MS(50), .audio_buffer = SC_TICK_FROM_MS(50),
.audio_output_buffer = SC_TICK_FROM_MS(5), .audio_output_buffer = SC_TICK_FROM_MS(5),
#ifdef HAVE_V4L2
.v4l2_device = NULL,
.v4l2_buffer = 0,
#endif
#ifdef HAVE_USB #ifdef HAVE_USB
.otg = false, .otg = false,
#endif #endif
@@ -52,8 +52,7 @@ const struct scrcpy_options scrcpy_options_default = {
.fullscreen = false, .fullscreen = false,
.always_on_top = false, .always_on_top = false,
.control = true, .control = true,
.video_playback = true, .mirror = true,
.audio_playback = true,
.turn_screen_off = false, .turn_screen_off = false,
.key_inject_mode = SC_KEY_INJECT_MODE_MIXED, .key_inject_mode = SC_KEY_INJECT_MODE_MIXED,
.window_borderless = false, .window_borderless = false,

View File

@@ -112,6 +112,9 @@ struct scrcpy_options {
const char *audio_codec_options; const char *audio_codec_options;
const char *video_encoder; const char *video_encoder;
const char *audio_encoder; const char *audio_encoder;
#ifdef HAVE_V4L2
const char *v4l2_device;
#endif
enum sc_log_level log_level; enum sc_log_level log_level;
enum sc_codec video_codec; enum sc_codec video_codec;
enum sc_codec audio_codec; enum sc_codec audio_codec;
@@ -134,12 +137,9 @@ struct scrcpy_options {
uint16_t window_height; uint16_t window_height;
uint32_t display_id; uint32_t display_id;
sc_tick display_buffer; sc_tick display_buffer;
sc_tick v4l2_buffer;
sc_tick audio_buffer; sc_tick audio_buffer;
sc_tick audio_output_buffer; sc_tick audio_output_buffer;
#ifdef HAVE_V4L2
const char *v4l2_device;
sc_tick v4l2_buffer;
#endif
#ifdef HAVE_USB #ifdef HAVE_USB
bool otg; bool otg;
#endif #endif
@@ -147,8 +147,7 @@ struct scrcpy_options {
bool fullscreen; bool fullscreen;
bool always_on_top; bool always_on_top;
bool control; bool control;
bool video_playback; bool mirror;
bool audio_playback;
bool turn_screen_off; bool turn_screen_off;
enum sc_key_inject_mode key_inject_mode; enum sc_key_inject_mode key_inject_mode;
bool window_borderless; bool window_borderless;

View File

@@ -137,7 +137,7 @@ sdl_set_hints(const char *render_driver) {
} }
static void static void
sdl_configure(bool video_playback, bool disable_screensaver) { sdl_configure(bool mirror, bool disable_screensaver) {
#ifdef _WIN32 #ifdef _WIN32
// Clean up properly on Ctrl+C on Windows // Clean up properly on Ctrl+C on Windows
bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE); bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE);
@@ -146,7 +146,7 @@ sdl_configure(bool video_playback, bool disable_screensaver) {
} }
#endif // _WIN32 #endif // _WIN32
if (!video_playback) { if (!mirror) {
return; return;
} }
@@ -386,26 +386,22 @@ scrcpy(struct scrcpy_options *options) {
goto end; goto end;
} }
// playback implies capture if (options->mirror) {
assert(!options->video_playback || options->video);
assert(!options->audio_playback || options->audio);
if (options->video_playback) {
sdl_set_hints(options->render_driver); sdl_set_hints(options->render_driver);
if (SDL_Init(SDL_INIT_VIDEO)) {
// Initialize SDL video and audio in addition if mirroring is enabled
if (options->video && SDL_Init(SDL_INIT_VIDEO)) {
LOGE("Could not initialize SDL video: %s", SDL_GetError()); LOGE("Could not initialize SDL video: %s", SDL_GetError());
goto end; goto end;
} }
}
if (options->audio_playback) { if (options->audio && SDL_Init(SDL_INIT_AUDIO)) {
if (SDL_Init(SDL_INIT_AUDIO)) {
LOGE("Could not initialize SDL audio: %s", SDL_GetError()); LOGE("Could not initialize SDL audio: %s", SDL_GetError());
goto end; goto end;
} }
} }
sdl_configure(options->video_playback, options->disable_screensaver); sdl_configure(options->mirror, options->disable_screensaver);
// Await for server without blocking Ctrl+C handling // Await for server without blocking Ctrl+C handling
bool connected; bool connected;
@@ -431,8 +427,7 @@ scrcpy(struct scrcpy_options *options) {
struct sc_file_pusher *fp = NULL; struct sc_file_pusher *fp = NULL;
// control implies video playback assert(!options->control || options->mirror); // control implies mirror
assert(!options->control || options->video_playback);
if (options->control) { if (options->control) {
if (!sc_file_pusher_init(&s->file_pusher, serial, if (!sc_file_pusher_init(&s->file_pusher, serial,
options->push_target)) { options->push_target)) {
@@ -458,8 +453,8 @@ scrcpy(struct scrcpy_options *options) {
&audio_demuxer_cbs, options); &audio_demuxer_cbs, options);
} }
bool needs_video_decoder = options->video_playback; bool needs_video_decoder = options->mirror && options->video;
bool needs_audio_decoder = options->audio_playback; bool needs_audio_decoder = options->mirror && options->audio;
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
needs_video_decoder |= !!options->v4l2_device; needs_video_decoder |= !!options->v4l2_device;
#endif #endif
@@ -639,12 +634,23 @@ aoa_hid_end:
} }
controller_started = true; controller_started = true;
controller = &s->controller; 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 // There is a controller if and only if control is enabled
assert(options->control == !!controller); assert(options->control == !!controller);
if (options->video_playback) { if (options->mirror) {
const char *window_title = const char *window_title =
options->window_title ? options->window_title : info->device_name; options->window_title ? options->window_title : info->device_name;
@@ -678,19 +684,21 @@ aoa_hid_end:
src = &s->display_buffer.frame_source; src = &s->display_buffer.frame_source;
} }
if (!sc_screen_init(&s->screen, &screen_params)) { if (options->video) {
goto end; if (!sc_screen_init(&s->screen, &screen_params)) {
goto end;
}
screen_initialized = true;
sc_frame_source_add_sink(src, &s->screen.frame_sink);
} }
screen_initialized = true;
sc_frame_source_add_sink(src, &s->screen.frame_sink); if (options->audio) {
} sc_audio_player_init(&s->audio_player, options->audio_buffer,
options->audio_output_buffer);
if (options->audio_playback) { sc_frame_source_add_sink(&s->audio_decoder.frame_source,
sc_audio_player_init(&s->audio_player, options->audio_buffer, &s->audio_player.frame_sink);
options->audio_output_buffer); }
sc_frame_source_add_sink(&s->audio_decoder.frame_source,
&s->audio_player.frame_sink);
} }
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
@@ -729,18 +737,6 @@ aoa_hid_end:
audio_demuxer_started = true; 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); ret = event_loop(s);
LOGD("quit..."); LOGD("quit...");

View File

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

View File

@@ -24,8 +24,6 @@ To disable audio:
scrcpy --no-audio scrcpy --no-audio
``` ```
To disable only the audio playback, see [no playback](video.md#no-playback).
## Audio only ## Audio only
To play audio only, disable the video: To play audio only, disable the video:

View File

@@ -21,10 +21,10 @@ scrcpy --no-video --audio-codec=aac --record-file=file.aac
# .m4a/.mp4 and .mka/.mkv are also supported for both opus and aac # .m4a/.mp4 and .mka/.mkv are also supported for both opus and aac
``` ```
To disable playback while recording: To disable mirroring while recording:
```bash ```bash
scrcpy --no-playback --record=file.mp4 scrcpy --no-mirror --record=file.mp4
scrcpy -Nr file.mkv scrcpy -Nr file.mkv
# interrupt recording with Ctrl+C # interrupt recording with Ctrl+C
``` ```

View File

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

View File

@@ -159,25 +159,15 @@ scrcpy --display-buffer=50 --v4l2-buffer=300
``` ```
## No playback ## No mirror
It is possible to capture an Android device without playing video or audio on It is possible to capture an Android device without displaying a mirroring
the computer. This option is useful when [recording](recording.md) or when window. This option is available if either [recording](recording.md) or
[v4l2](#video4linux) is enabled: [v4l2](#video4linux) is enabled:
```bash ```bash
scrcpy --v4l2-sink=/dev/video2 --no-playback scrcpy --v4l2-sink=/dev/video2 --no-mirror
scrcpy --record=file.mkv --no-playback scrcpy --record=file.mkv --no-mirror
```
It is also possible to disable video and audio playback separately:
```bash
# Send video to V4L2 sink without playing it, but keep audio playback
scrcpy --v4l2-sink=/dev/video2 --no-video-playback
# Record both video and audio, but only play video
scrcpy --record=file.mkv --no-audio-playback
``` ```

View File

@@ -1,26 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view;
/**
* {@hide}
*/
oneway interface IDisplayFoldListener
{
/** Called when the foldedness of a display changes */
void onDisplayFoldChanged(int displayId, boolean folded);
}

View File

@@ -12,7 +12,6 @@ import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.os.SystemClock; import android.os.SystemClock;
import android.view.IRotationWatcher; import android.view.IRotationWatcher;
import android.view.IDisplayFoldListener;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.InputEvent; import android.view.InputEvent;
import android.view.KeyCharacterMap; import android.view.KeyCharacterMap;
@@ -36,10 +35,6 @@ public final class Device {
void onRotationChanged(int rotation); void onRotationChanged(int rotation);
} }
public interface FoldListener {
void onFoldChanged(int displayId, boolean folded);
}
public interface ClipboardListener { public interface ClipboardListener {
void onClipboardTextChanged(String text); void onClipboardTextChanged(String text);
} }
@@ -51,7 +46,6 @@ public final class Device {
private ScreenInfo screenInfo; private ScreenInfo screenInfo;
private RotationListener rotationListener; private RotationListener rotationListener;
private FoldListener foldListener;
private ClipboardListener clipboardListener; private ClipboardListener clipboardListener;
private final AtomicBoolean isSettingClipboard = new AtomicBoolean(); private final AtomicBoolean isSettingClipboard = new AtomicBoolean();
@@ -99,26 +93,6 @@ public final class Device {
} }
}, displayId); }, displayId);
ServiceManager.getWindowManager().registerDisplayFoldListener(new IDisplayFoldListener.Stub() {
@Override
public void onDisplayFoldChanged(int displayId, boolean folded) {
synchronized (Device.this) {
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
if (displayInfo == null) {
Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage());
return;
}
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), displayInfo.getSize(), options.getCrop(),
options.getMaxSize(), options.getLockVideoOrientation());
// notify
if (foldListener != null) {
foldListener.onFoldChanged(displayId, folded);
}
}
}
});
if (options.getControl() && options.getClipboardAutosync()) { if (options.getControl() && options.getClipboardAutosync()) {
// If control and autosync are enabled, synchronize Android clipboard to the computer automatically // If control and autosync are enabled, synchronize Android clipboard to the computer automatically
ClipboardManager clipboardManager = ServiceManager.getClipboardManager(); ClipboardManager clipboardManager = ServiceManager.getClipboardManager();
@@ -250,10 +224,6 @@ public final class Device {
this.rotationListener = rotationListener; this.rotationListener = rotationListener;
} }
public synchronized void setFoldListener(FoldListener foldlistener) {
this.foldListener = foldlistener;
}
public synchronized void setClipboardListener(ClipboardListener clipboardListener) { public synchronized void setClipboardListener(ClipboardListener clipboardListener) {
this.clipboardListener = clipboardListener; this.clipboardListener = clipboardListener;
} }

View File

@@ -16,7 +16,7 @@ import java.nio.ByteBuffer;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public class ScreenEncoder implements Device.RotationListener, Device.FoldListener, AsyncProcessor { public class ScreenEncoder implements Device.RotationListener, AsyncProcessor {
private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds
private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms
@@ -26,7 +26,7 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen
private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800}; private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800};
private static final int MAX_CONSECUTIVE_ERRORS = 3; private static final int MAX_CONSECUTIVE_ERRORS = 3;
private final AtomicBoolean resetCapture = new AtomicBoolean(); private final AtomicBoolean rotationChanged = new AtomicBoolean();
private final Device device; private final Device device;
private final Streamer streamer; private final Streamer streamer;
@@ -53,18 +53,13 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen
this.downsizeOnError = downsizeOnError; this.downsizeOnError = downsizeOnError;
} }
@Override
public void onFoldChanged(int displayId, boolean folded) {
resetCapture.set(true);
}
@Override @Override
public void onRotationChanged(int rotation) { public void onRotationChanged(int rotation) {
resetCapture.set(true); rotationChanged.set(true);
} }
private boolean consumeResetCapture() { private boolean consumeRotationChange() {
return resetCapture.getAndSet(false); return rotationChanged.getAndSet(false);
} }
private void streamScreen() throws IOException, ConfigurationException { private void streamScreen() throws IOException, ConfigurationException {
@@ -73,7 +68,6 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions); MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
IBinder display = createDisplay(); IBinder display = createDisplay();
device.setRotationListener(this); device.setRotationListener(this);
device.setFoldListener(this);
streamer.writeVideoHeader(device.getScreenInfo().getVideoSize()); streamer.writeVideoHeader(device.getScreenInfo().getVideoSize());
@@ -121,7 +115,6 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen
} finally { } finally {
mediaCodec.release(); mediaCodec.release();
device.setRotationListener(null); device.setRotationListener(null);
device.setFoldListener(null);
SurfaceControl.destroyDisplay(display); SurfaceControl.destroyDisplay(display);
} }
} }
@@ -176,14 +169,14 @@ public class ScreenEncoder implements Device.RotationListener, Device.FoldListen
boolean alive = true; boolean alive = true;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while (!consumeResetCapture() && !eof) { while (!consumeRotationChange() && !eof) {
if (stopped.get()) { if (stopped.get()) {
alive = false; alive = false;
break; break;
} }
int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1); int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
try { try {
if (consumeResetCapture()) { if (consumeRotationChange()) {
// must restart encoding with new size // must restart encoding with new size
break; break;
} }

View File

@@ -4,7 +4,6 @@ import com.genymobile.scrcpy.Ln;
import android.os.IInterface; import android.os.IInterface;
import android.view.IRotationWatcher; import android.view.IRotationWatcher;
import android.view.IDisplayFoldListener;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@@ -109,13 +108,4 @@ public final class WindowManager {
throw new AssertionError(e); throw new AssertionError(e);
} }
} }
public void registerDisplayFoldListener(IDisplayFoldListener foldListener) {
try {
Class<?> cls = manager.getClass();
cls.getMethod("registerDisplayFoldListener", IDisplayFoldListener.class).invoke(manager, foldListener);
} catch (Exception e) {
throw new AssertionError(e);
}
}
} }