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
26 changed files with 149 additions and 400 deletions
+1 -8
View File
@@ -7,7 +7,6 @@ _scrcpy() {
--audio-codec= --audio-codec=
--audio-codec-options= --audio-codec-options=
--audio-encoder= --audio-encoder=
--audio-source=
--audio-output-buffer= --audio-output-buffer=
-b --video-bit-rate= -b --video-bit-rate=
--crop= --crop=
@@ -30,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
@@ -87,10 +84,6 @@ _scrcpy() {
COMPREPLY=($(compgen -W 'opus aac raw' -- "$cur")) COMPREPLY=($(compgen -W 'opus aac raw' -- "$cur"))
return return
;; ;;
--audio-source)
COMPREPLY=($(compgen -W 'output mic' -- "$cur"))
return
;;
--lock-video-orientation) --lock-video-orientation)
COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur")) COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur"))
return return
+1 -4
View File
@@ -14,7 +14,6 @@ arguments=(
'--audio-codec=[Select the audio codec]:codec:(opus aac raw)' '--audio-codec=[Select the audio codec]:codec:(opus aac raw)'
'--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]' '--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]'
'--audio-encoder=[Use a specific MediaCodec audio encoder]' '--audio-encoder=[Use a specific MediaCodec audio encoder]'
'--audio-source=[Select the audio source]:source:(output mic)'
'--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]' '--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]'
{-b,--video-bit-rate=}'[Encode the video at the given bit-rate]' {-b,--video-bit-rate=}'[Encode the video at the given bit-rate]'
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
@@ -36,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]'
+10 -24
View File
@@ -33,6 +33,14 @@ Lower values decrease the latency, but increase the likelyhood of buffer underru
Default is 50. Default is 50.
.TP
.BI "\-\-audio\-output\-buffer ms
Configure the size of the SDL audio output buffer (in milliseconds).
If you get "robotic" audio playback, you should test with a higher value (10). Do not change this setting otherwise.
Default is 5.
.TP .TP
.BI "\-\-audio\-codec " name .BI "\-\-audio\-codec " name
Select an audio codec (opus, aac or raw). Select an audio codec (opus, aac or raw).
@@ -55,20 +63,6 @@ Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\
The available encoders can be listed by \-\-list\-encoders. The available encoders can be listed by \-\-list\-encoders.
.TP
.BI "\-\-audio\-source " source
Select the audio source (output or mic).
Default is output.
.TP
.BI "\-\-audio\-output\-buffer ms
Configure the size of the SDL audio output buffer (in milliseconds).
If you get "robotic" audio playback, you should test with a higher value (10). Do not change this setting otherwise.
Default is 5.
.TP .TP
.BI "\-b, \-\-video\-bit\-rate " value .BI "\-b, \-\-video\-bit\-rate " value
Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000). Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
@@ -193,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.
@@ -220,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
@@ -239,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.
+1 -1
View File
@@ -107,7 +107,7 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
// latency. // latency.
LOGD("[Audio] Buffer underflow, inserting silence: %" PRIu32 " samples", LOGD("[Audio] Buffer underflow, inserting silence: %" PRIu32 " samples",
silence); silence);
memset(stream + TO_BYTES(read), 0, TO_BYTES(silence)); memset(stream + read, 0, TO_BYTES(silence));
if (ap->received) { if (ap->received) {
// Inserting additional samples immediately increases buffering // Inserting additional samples immediately increases buffering
+54 -111
View File
@@ -74,9 +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,
OPT_AUDIO_SOURCE,
}; };
struct sc_option { struct sc_option {
@@ -135,6 +132,16 @@ static const struct sc_option options[] = {
"likelyhood of buffer underrun (causing audio glitches).\n" "likelyhood of buffer underrun (causing audio glitches).\n"
"Default is 50.", "Default is 50.",
}, },
{
.longopt_id = OPT_AUDIO_OUTPUT_BUFFER,
.longopt = "audio-output-buffer",
.argdesc = "ms",
.text = "Configure the size of the SDL audio output buffer (in "
"milliseconds).\n"
"If you get \"robotic\" audio playback, you should test with "
"a higher value (10). Do not change this setting otherwise.\n"
"Default is 5.",
},
{ {
.longopt_id = OPT_AUDIO_CODEC, .longopt_id = OPT_AUDIO_CODEC,
.longopt = "audio-codec", .longopt = "audio-codec",
@@ -162,23 +169,6 @@ static const struct sc_option options[] = {
"codec provided by --audio-codec).\n" "codec provided by --audio-codec).\n"
"The available encoders can be listed by --list-encoders.", "The available encoders can be listed by --list-encoders.",
}, },
{
.longopt_id = OPT_AUDIO_SOURCE,
.longopt = "audio-source",
.argdesc = "source",
.text = "Select the audio source (output or mic).\n"
"Default is output.",
},
{
.longopt_id = OPT_AUDIO_OUTPUT_BUFFER,
.longopt = "audio-output-buffer",
.argdesc = "ms",
.text = "Configure the size of the SDL audio output buffer (in "
"milliseconds).\n"
"If you get \"robotic\" audio playback, you should test with "
"a higher value (10). Do not change this setting otherwise.\n"
"Default is 5.",
},
{ {
.shortopt = 'b', .shortopt = 'b',
.longopt = "video-bit-rate", .longopt = "video-bit-rate",
@@ -361,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",
@@ -397,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
@@ -428,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",
@@ -1596,22 +1576,6 @@ parse_audio_codec(const char *optarg, enum sc_codec *codec) {
return false; return false;
} }
static bool
parse_audio_source(const char *optarg, enum sc_audio_source *source) {
if (!strcmp(optarg, "mic")) {
*source = SC_AUDIO_SOURCE_MIC;
return true;
}
if (!strcmp(optarg, "output")) {
*source = SC_AUDIO_SOURCE_OUTPUT;
return true;
}
LOGE("Unsupported audio source: %s (expected output or mic)", optarg);
return false;
}
static bool static bool
parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
const char *optstring, const struct option *longopts) { const char *optstring, const struct option *longopts) {
@@ -1707,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)) {
@@ -1915,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:
@@ -1939,11 +1895,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false; return false;
} }
break; break;
case OPT_AUDIO_SOURCE:
if (!parse_audio_source(optarg, &opts->audio_source)) {
return false;
}
break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
return false; return false;
@@ -1972,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. "
@@ -2035,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.");
@@ -2116,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 "
@@ -2127,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) {
@@ -2154,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;
+3 -2
View File
@@ -79,8 +79,9 @@ sc_demuxer_recv_video_size(struct sc_demuxer *demuxer, uint32_t *width,
static bool static bool
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) { sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
// The video and audio streams contain a sequence of raw packets (as // The video stream contains raw packets, without time information. When we
// provided by MediaCodec), each prefixed with a "meta" header. // record, we retrieve the timestamps separately, from a "meta" header
// added by the server before each raw packet.
// //
// The "meta" header length is 12 bytes: // The "meta" header length is 12 bytes:
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ... // [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
+5 -7
View File
@@ -11,10 +11,12 @@ 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,
.audio_source = SC_AUDIO_SOURCE_OUTPUT,
.record_format = SC_RECORD_FORMAT_AUTO, .record_format = SC_RECORD_FORMAT_AUTO,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT,
.mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT, .mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT,
@@ -40,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
@@ -53,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,
+5 -12
View File
@@ -44,11 +44,6 @@ enum sc_codec {
SC_CODEC_RAW, SC_CODEC_RAW,
}; };
enum sc_audio_source {
SC_AUDIO_SOURCE_OUTPUT,
SC_AUDIO_SOURCE_MIC,
};
enum sc_lock_video_orientation { enum sc_lock_video_orientation {
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1, SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
// lock the current orientation when scrcpy starts // lock the current orientation when scrcpy starts
@@ -117,10 +112,12 @@ 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;
enum sc_audio_source audio_source;
enum sc_record_format record_format; enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode; enum sc_keyboard_input_mode keyboard_input_mode;
enum sc_mouse_input_mode mouse_input_mode; enum sc_mouse_input_mode mouse_input_mode;
@@ -140,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
@@ -153,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;
+36 -41
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;
} }
@@ -334,7 +334,6 @@ scrcpy(struct scrcpy_options *options) {
.log_level = options->log_level, .log_level = options->log_level,
.video_codec = options->video_codec, .video_codec = options->video_codec,
.audio_codec = options->audio_codec, .audio_codec = options->audio_codec,
.audio_source = options->audio_source,
.crop = options->crop, .crop = options->crop,
.port_range = options->port_range, .port_range = options->port_range,
.tunnel_host = options->tunnel_host, .tunnel_host = options->tunnel_host,
@@ -387,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;
@@ -432,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)) {
@@ -459,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
@@ -640,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;
@@ -679,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
@@ -730,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...");
-4
View File
@@ -246,10 +246,6 @@ execute_server(struct sc_server *server,
ADD_PARAM("audio_codec=%s", ADD_PARAM("audio_codec=%s",
sc_server_get_codec_name(params->audio_codec)); sc_server_get_codec_name(params->audio_codec));
} }
if (params->audio_source != SC_AUDIO_SOURCE_OUTPUT) {
assert(params->audio_source == SC_AUDIO_SOURCE_MIC);
ADD_PARAM("audio_source=mic");
}
if (params->max_size) { if (params->max_size) {
ADD_PARAM("max_size=%" PRIu16, params->max_size); ADD_PARAM("max_size=%" PRIu16, params->max_size);
} }
-1
View File
@@ -26,7 +26,6 @@ struct sc_server_params {
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;
enum sc_audio_source audio_source;
const char *crop; const char *crop;
const char *video_codec_options; const char *video_codec_options;
const char *audio_codec_options; const char *audio_codec_options;
+4 -5
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);
} }
-20
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:
@@ -41,24 +39,6 @@ interesting to add [buffering](#buffering) to minimize glitches:
scrcpy --no-video --audio-buffer=200 scrcpy --no-video --audio-buffer=200
``` ```
## Source
By default, the device audio output is forwarded.
It is possible to capture the device microphone instead:
```
scrcpy --audio-source=mic
```
For example, to use the device as a dictaphone and record a capture directly on
the computer:
```
scrcpy --audio-source=mic --no-video --no-audio-playback --record=file.opus
```
## Codec ## Codec
The audio codec can be selected. The possible values are `opus` (default), `aac` The audio codec can be selected. The possible values are `opus` (default), `aac`
+2 -2
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
``` ```
+1 -1
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*`)
+5 -15
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
``` ```
@@ -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);
}
@@ -10,6 +10,7 @@ import android.media.AudioFormat;
import android.media.AudioRecord; import android.media.AudioRecord;
import android.media.AudioTimestamp; import android.media.AudioTimestamp;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaRecorder;
import android.os.Build; import android.os.Build;
import android.os.SystemClock; import android.os.SystemClock;
@@ -17,6 +18,7 @@ import java.nio.ByteBuffer;
public final class AudioCapture { public final class AudioCapture {
public static final int SOURCE = MediaRecorder.AudioSource.REMOTE_SUBMIX;
public static final int SAMPLE_RATE = 48000; public static final int SAMPLE_RATE = 48000;
public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;
public static final int CHANNELS = 2; public static final int CHANNELS = 2;
@@ -24,18 +26,12 @@ public final class AudioCapture {
public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT; public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;
public static final int BYTES_PER_SAMPLE = 2; public static final int BYTES_PER_SAMPLE = 2;
private final int audioSource;
private AudioRecord recorder; private AudioRecord recorder;
private final AudioTimestamp timestamp = new AudioTimestamp(); private final AudioTimestamp timestamp = new AudioTimestamp();
private long previousPts = 0; private long previousPts = 0;
private long nextPts = 0; private long nextPts = 0;
public AudioCapture(AudioSource audioSource) {
this.audioSource = audioSource.value();
}
public static int millisToBytes(int millis) { public static int millisToBytes(int millis) {
return SAMPLE_RATE * CHANNELS * BYTES_PER_SAMPLE * millis / 1000; return SAMPLE_RATE * CHANNELS * BYTES_PER_SAMPLE * millis / 1000;
} }
@@ -50,13 +46,13 @@ public final class AudioCapture {
@TargetApi(Build.VERSION_CODES.M) @TargetApi(Build.VERSION_CODES.M)
@SuppressLint({"WrongConstant", "MissingPermission"}) @SuppressLint({"WrongConstant", "MissingPermission"})
private static AudioRecord createAudioRecord(int audioSource) { private static AudioRecord createAudioRecord() {
AudioRecord.Builder builder = new AudioRecord.Builder(); AudioRecord.Builder builder = new AudioRecord.Builder();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// On older APIs, Workarounds.fillAppInfo() must be called beforehand // On older APIs, Workarounds.fillAppInfo() must be called beforehand
builder.setContext(FakeContext.get()); builder.setContext(FakeContext.get());
} }
builder.setAudioSource(audioSource); builder.setAudioSource(SOURCE);
builder.setAudioFormat(createAudioFormat()); builder.setAudioFormat(createAudioFormat());
int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, ENCODING); int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, ENCODING);
// This buffer size does not impact latency // This buffer size does not impact latency
@@ -104,12 +100,12 @@ public final class AudioCapture {
private void startRecording() { private void startRecording() {
try { try {
recorder = createAudioRecord(audioSource); recorder = createAudioRecord();
} catch (NullPointerException e) { } catch (NullPointerException e) {
// Creating an AudioRecord using an AudioRecord.Builder does not work on Vivo phones: // Creating an AudioRecord using an AudioRecord.Builder does not work on Vivo phones:
// - <https://github.com/Genymobile/scrcpy/issues/3805> // - <https://github.com/Genymobile/scrcpy/issues/3805>
// - <https://github.com/Genymobile/scrcpy/pull/3862> // - <https://github.com/Genymobile/scrcpy/pull/3862>
recorder = Workarounds.createAudioRecord(audioSource, SAMPLE_RATE, CHANNEL_CONFIG, CHANNELS, CHANNEL_MASK, ENCODING); recorder = Workarounds.createAudioRecord(SOURCE, SAMPLE_RATE, CHANNEL_CONFIG, CHANNELS, CHANNEL_MASK, ENCODING);
} }
recorder.startRecording(); recorder.startRecording();
} }
@@ -40,7 +40,6 @@ public final class AudioEncoder implements AsyncProcessor {
private static final int READ_MS = 5; // milliseconds private static final int READ_MS = 5; // milliseconds
private static final int READ_SIZE = AudioCapture.millisToBytes(READ_MS); private static final int READ_SIZE = AudioCapture.millisToBytes(READ_MS);
private final AudioCapture capture;
private final Streamer streamer; private final Streamer streamer;
private final int bitRate; private final int bitRate;
private final List<CodecOption> codecOptions; private final List<CodecOption> codecOptions;
@@ -59,8 +58,7 @@ public final class AudioEncoder implements AsyncProcessor {
private boolean ended; private boolean ended;
public AudioEncoder(AudioCapture capture, Streamer streamer, int bitRate, List<CodecOption> codecOptions, String encoderName) { public AudioEncoder(Streamer streamer, int bitRate, List<CodecOption> codecOptions, String encoderName) {
this.capture = capture;
this.streamer = streamer; this.streamer = streamer;
this.bitRate = bitRate; this.bitRate = bitRate;
this.codecOptions = codecOptions; this.codecOptions = codecOptions;
@@ -177,6 +175,7 @@ public final class AudioEncoder implements AsyncProcessor {
} }
MediaCodec mediaCodec = null; MediaCodec mediaCodec = null;
AudioCapture capture = new AudioCapture();
boolean mediaCodecStarted = false; boolean mediaCodecStarted = false;
try { try {
@@ -193,9 +192,10 @@ public final class AudioEncoder implements AsyncProcessor {
capture.start(); capture.start();
final MediaCodec mediaCodecRef = mediaCodec; final MediaCodec mediaCodecRef = mediaCodec;
final AudioCapture captureRef = capture;
inputThread = new Thread(() -> { inputThread = new Thread(() -> {
try { try {
inputThread(mediaCodecRef, capture); inputThread(mediaCodecRef, captureRef);
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
Ln.e("Audio capture error", e); Ln.e("Audio capture error", e);
} finally { } finally {
@@ -8,7 +8,6 @@ import java.nio.ByteBuffer;
public final class AudioRawRecorder implements AsyncProcessor { public final class AudioRawRecorder implements AsyncProcessor {
private final AudioCapture capture;
private final Streamer streamer; private final Streamer streamer;
private Thread thread; private Thread thread;
@@ -16,8 +15,7 @@ public final class AudioRawRecorder implements AsyncProcessor {
private static final int READ_MS = 5; // milliseconds private static final int READ_MS = 5; // milliseconds
private static final int READ_SIZE = AudioCapture.millisToBytes(READ_MS); private static final int READ_SIZE = AudioCapture.millisToBytes(READ_MS);
public AudioRawRecorder(AudioCapture capture, Streamer streamer) { public AudioRawRecorder(Streamer streamer) {
this.capture = capture;
this.streamer = streamer; this.streamer = streamer;
} }
@@ -31,6 +29,7 @@ public final class AudioRawRecorder implements AsyncProcessor {
final ByteBuffer buffer = ByteBuffer.allocateDirect(READ_SIZE); final ByteBuffer buffer = ByteBuffer.allocateDirect(READ_SIZE);
final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
AudioCapture capture = new AudioCapture();
try { try {
capture.start(); capture.start();
@@ -1,30 +0,0 @@
package com.genymobile.scrcpy;
import android.media.MediaRecorder;
public enum AudioSource {
OUTPUT("output", MediaRecorder.AudioSource.REMOTE_SUBMIX),
MIC("mic", MediaRecorder.AudioSource.MIC);
private final String name;
private final int value;
AudioSource(String name, int value) {
this.name = name;
this.value = value;
}
int value() {
return value;
}
static AudioSource findByName(String name) {
for (AudioSource audioSource : AudioSource.values()) {
if (name.equals(audioSource.name)) {
return audioSource;
}
}
return null;
}
}
@@ -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;
} }
@@ -14,7 +14,6 @@ public class Options {
private int maxSize; private int maxSize;
private VideoCodec videoCodec = VideoCodec.H264; private VideoCodec videoCodec = VideoCodec.H264;
private AudioCodec audioCodec = AudioCodec.OPUS; private AudioCodec audioCodec = AudioCodec.OPUS;
private AudioSource audioSource = AudioSource.OUTPUT;
private int videoBitRate = 8000000; private int videoBitRate = 8000000;
private int audioBitRate = 128000; private int audioBitRate = 128000;
private int maxFps; private int maxFps;
@@ -73,10 +72,6 @@ public class Options {
return audioCodec; return audioCodec;
} }
public AudioSource getAudioSource() {
return audioSource;
}
public int getVideoBitRate() { public int getVideoBitRate() {
return videoBitRate; return videoBitRate;
} }
@@ -230,13 +225,6 @@ public class Options {
} }
options.audioCodec = audioCodec; options.audioCodec = audioCodec;
break; break;
case "audio_source":
AudioSource audioSource = AudioSource.findByName(value);
if (audioSource == null) {
throw new IllegalArgumentException("Audio source " + value + " not supported");
}
options.audioSource = audioSource;
break;
case "max_size": case "max_size":
options.maxSize = Integer.parseInt(value) & ~7; // multiple of 8 options.maxSize = Integer.parseInt(value) & ~7; // multiple of 8
break; break;
@@ -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;
} }
@@ -136,14 +136,13 @@ public final class Server {
if (audio) { if (audio) {
AudioCodec audioCodec = options.getAudioCodec(); AudioCodec audioCodec = options.getAudioCodec();
AudioCapture audioCapture = new AudioCapture(options.getAudioSource());
Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(), Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(),
options.getSendFrameMeta()); options.getSendFrameMeta());
AsyncProcessor audioRecorder; AsyncProcessor audioRecorder;
if (audioCodec == AudioCodec.RAW) { if (audioCodec == AudioCodec.RAW) {
audioRecorder = new AudioRawRecorder(audioCapture, audioStreamer); audioRecorder = new AudioRawRecorder(audioStreamer);
} else { } else {
audioRecorder = new AudioEncoder(audioCapture, audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(), audioRecorder = new AudioEncoder(audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(),
options.getAudioEncoder()); options.getAudioEncoder());
} }
asyncProcessors.add(audioRecorder); asyncProcessors.add(audioRecorder);
@@ -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);
}
}
} }