Compare commits

..

1 Commits

Author SHA1 Message Date
Romain Vimont
d25f38266d Extract device connection to a separate doc page
Create a new "Connection" documentation page.
2023-07-14 23:21:14 +02:00
26 changed files with 102 additions and 331 deletions

View File

@@ -5,7 +5,7 @@
_pronounced "**scr**een **c**o**py**"_ _pronounced "**scr**een **c**o**py**"_
This application mirrors Android devices (video and audio) connected via This application mirrors Android devices (video and audio) connected via
USB or [over TCP/IP](doc/connection.md#tcpip-wireless), and allows to control the USB or [over TCP/IP](doc/device.md#tcpip-wireless), and allows to control the
device with the keyboard and the mouse of the computer. It does not require any device with the keyboard and the mouse of the computer. It does not require any
_root_ access. It works on _Linux_, _Windows_ and _macOS_. _root_ access. It works on _Linux_, _Windows_ and _macOS_.

View File

@@ -23,7 +23,6 @@ _scrcpy() {
--kill-adb-on-close --kill-adb-on-close
-K --hid-keyboard -K --hid-keyboard
--legacy-paste --legacy-paste
--list-cameras
--list-displays --list-displays
--list-encoders --list-encoders
--lock-video-orientation --lock-video-orientation

View File

@@ -30,7 +30,6 @@ arguments=(
'--kill-adb-on-close[Kill adb when scrcpy terminates]' '--kill-adb-on-close[Kill adb when scrcpy terminates]'
{-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]' {-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]'
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
'--list-cameras[List cameras available on the device]'
'--list-displays[List displays available on the device]' '--list-displays[List displays available on the device]'
'--list-encoders[List video and audio encoders available on the device]' '--list-encoders[List video and audio encoders available on the device]'
'--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)' '--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)'

View File

@@ -155,9 +155,6 @@ Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+S
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically. This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
.B \-\-list\-cameras
List cameras available on the device.
.TP .TP
.B \-\-list\-encoders .B \-\-list\-encoders
List video and audio encoders available on the device. List video and audio encoders available on the device.

View File

@@ -79,7 +79,6 @@ enum {
OPT_AUDIO_SOURCE, OPT_AUDIO_SOURCE,
OPT_KILL_ADB_ON_CLOSE, OPT_KILL_ADB_ON_CLOSE,
OPT_TIME_LIMIT, OPT_TIME_LIMIT,
OPT_LIST_CAMERAS,
}; };
struct sc_option { struct sc_option {
@@ -313,11 +312,6 @@ static const struct sc_option options[] = {
"This is a workaround for some devices not behaving as " "This is a workaround for some devices not behaving as "
"expected when setting the device clipboard programmatically.", "expected when setting the device clipboard programmatically.",
}, },
{
.longopt_id = OPT_LIST_CAMERAS,
.longopt = "list-cameras",
.text = "List device cameras.",
},
{ {
.longopt_id = OPT_LIST_DISPLAYS, .longopt_id = OPT_LIST_DISPLAYS,
.longopt = "list-displays", .longopt = "list-displays",
@@ -1950,9 +1944,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
"platform)."); "platform).");
return false; return false;
#endif #endif
case OPT_LIST_CAMERAS:
opts->list_cameras = true;
break;
case OPT_LIST_ENCODERS: case OPT_LIST_ENCODERS:
opts->list_encoders = true; opts->list_encoders = true;
break; break;
@@ -2031,6 +2022,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->audio_playback = false; 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 if (opts->video && !opts->video_playback && !opts->record_filename
&& !v4l2) { && !v4l2) {
LOGI("No video playback, no recording, no V4L2 sink: video disabled"); LOGI("No video playback, no recording, no V4L2 sink: video disabled");

View File

@@ -53,7 +53,7 @@ sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) {
display->mipmaps = true; display->mipmaps = true;
} else { } else {
LOGW("Trilinear filtering disabled " LOGW("Trilinear filtering disabled "
"(OpenGL 3.0+ or ES 2.0+ required)"); "(OpenGL 3.0+ or ES 2.0+ required");
} }
} else { } else {
LOGI("Trilinear filtering disabled"); LOGI("Trilinear filtering disabled");

View File

@@ -81,6 +81,5 @@ const struct scrcpy_options scrcpy_options_default = {
.require_audio = false, .require_audio = false,
.list_encoders = false, .list_encoders = false,
.list_displays = false, .list_displays = false,
.list_cameras = false,
.kill_adb_on_close = false, .kill_adb_on_close = false,
}; };

View File

@@ -181,7 +181,6 @@ struct scrcpy_options {
bool require_audio; bool require_audio;
bool list_encoders; bool list_encoders;
bool list_displays; bool list_displays;
bool list_cameras;
bool kill_adb_on_close; bool kill_adb_on_close;
}; };

View File

@@ -252,9 +252,7 @@ sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer,
// Contrary to the video demuxer, keep mirroring if only the audio fails // Contrary to the video demuxer, keep mirroring if only the audio fails
// (unless --require-audio is set). // (unless --require-audio is set).
if (status == SC_DEMUXER_STATUS_EOS) { if (status == SC_DEMUXER_STATUS_ERROR
PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED);
} else if (status == SC_DEMUXER_STATUS_ERROR
|| (status == SC_DEMUXER_STATUS_DISABLED || (status == SC_DEMUXER_STATUS_DISABLED
&& options->require_audio)) { && options->require_audio)) {
PUSH_EVENT(SC_EVENT_DEMUXER_ERROR); PUSH_EVENT(SC_EVENT_DEMUXER_ERROR);
@@ -381,7 +379,6 @@ scrcpy(struct scrcpy_options *options) {
.power_on = options->power_on, .power_on = options->power_on,
.list_encoders = options->list_encoders, .list_encoders = options->list_encoders,
.list_displays = options->list_displays, .list_displays = options->list_displays,
.list_cameras = options->list_cameras,
.kill_adb_on_close = options->kill_adb_on_close, .kill_adb_on_close = options->kill_adb_on_close,
}; };
@@ -400,8 +397,7 @@ scrcpy(struct scrcpy_options *options) {
server_started = true; server_started = true;
if (options->list_encoders || options->list_displays if (options->list_encoders || options->list_displays) {
|| options->list_cameras) {
bool ok = await_for_server(NULL); bool ok = await_for_server(NULL);
ret = ok ? SCRCPY_EXIT_SUCCESS : SCRCPY_EXIT_FAILURE; ret = ok ? SCRCPY_EXIT_SUCCESS : SCRCPY_EXIT_FAILURE;
goto end; goto end;
@@ -452,7 +448,9 @@ scrcpy(struct scrcpy_options *options) {
struct sc_file_pusher *fp = NULL; struct sc_file_pusher *fp = NULL;
if (options->video_playback && options->control) { // control implies video playback
assert(!options->control || options->video_playback);
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)) {
goto end; goto end;

View File

@@ -316,9 +316,6 @@ execute_server(struct sc_server *server,
if (params->list_displays) { if (params->list_displays) {
ADD_PARAM("list_displays=true"); ADD_PARAM("list_displays=true");
} }
if (params->list_cameras) {
ADD_PARAM("list_cameras=true");
}
#undef ADD_PARAM #undef ADD_PARAM
@@ -898,8 +895,7 @@ run_server(void *data) {
// If --list-* is passed, then the server just prints the requested data // If --list-* is passed, then the server just prints the requested data
// then exits. // then exits.
if (params->list_encoders || params->list_displays if (params->list_encoders || params->list_displays) {
|| params->list_cameras) {
sc_pid pid = execute_server(server, params); sc_pid pid = execute_server(server, params);
if (pid == SC_PROCESS_NONE) { if (pid == SC_PROCESS_NONE) {
goto error_connection_failed; goto error_connection_failed;

View File

@@ -58,7 +58,6 @@ struct sc_server_params {
bool power_on; bool power_on;
bool list_encoders; bool list_encoders;
bool list_displays; bool list_displays;
bool list_cameras;
bool kill_adb_on_close; bool kill_adb_on_close;
}; };

View File

@@ -77,13 +77,6 @@ In particular, if you get the following error:
then your device has no Opus encoder: try `scrcpy --audio-codec=aac`. then your device has no Opus encoder: try `scrcpy --audio-codec=aac`.
For advanced usage, to pass arbitrary parameters to the [`MediaFormat`],
check `--audio-codec-options` in the manpage or in `scrcpy --help`.
[`MediaFormat`]: https://developer.android.com/reference/android/media/MediaFormat
## Encoder
Several encoders may be available on the device. They can be listed by: Several encoders may be available on the device. They can be listed by:
@@ -93,10 +86,15 @@ scrcpy --list-encoders
To select a specific encoder: To select a specific encoder:
```bash ```
scrcpy --audio-codec=opus --audio-encoder='c2.android.opus.encoder' scrcpy --audio-codec=opus --audio-encoder='c2.android.opus.encoder'
``` ```
For advanced usage, to pass arbitrary parameters to the [`MediaFormat`],
check `--audio-codec-options` in the manpage or in `scrcpy --help`.
[`MediaFormat`]: https://developer.android.com/reference/android/media/MediaFormat
## Bit rate ## Bit rate

View File

@@ -1,7 +1,23 @@
# Device # Device
Some command line arguments perform actions on the device itself while scrcpy is ## Display
running.
If several displays are available on the Android device, it is possible to
select the display to mirror:
```bash
scrcpy --display=1
```
The list of display ids can be retrieved by:
```bash
scrcpy --list-displays
```
A secondary display may only be controlled if the device runs at least Android
10 (otherwise it is mirrored as read-only).
## Stay awake ## Stay awake
@@ -78,3 +94,4 @@ By default, on start, the device is powered on. To prevent this behavior:
```bash ```bash
scrcpy --no-power-on scrcpy --no-power-on
``` ```

View File

@@ -66,14 +66,6 @@ scrcpy --video-codec=av1
H265 may provide better quality, but H264 should provide lower latency. H265 may provide better quality, but H264 should provide lower latency.
AV1 encoders are not common on current Android devices. AV1 encoders are not common on current Android devices.
For advanced usage, to pass arbitrary parameters to the [`MediaFormat`],
check `--video-codec-options` in the manpage or in `scrcpy --help`.
[`MediaFormat`]: https://developer.android.com/reference/android/media/MediaFormat
## Encoder
Several encoders may be available on the device. They can be listed by: Several encoders may be available on the device. They can be listed by:
```bash ```bash
@@ -87,6 +79,11 @@ try another one:
scrcpy --video-codec=h264 --video-encoder='OMX.qcom.video.encoder.avc' scrcpy --video-codec=h264 --video-encoder='OMX.qcom.video.encoder.avc'
``` ```
For advanced usage, to pass arbitrary parameters to the [`MediaFormat`],
check `--video-codec-options` in the manpage or in `scrcpy --help`.
[`MediaFormat`]: https://developer.android.com/reference/android/media/MediaFormat
## Rotation ## Rotation
@@ -137,25 +134,6 @@ phone, landscape for a tablet).
If `--max-size` is also specified, resizing is applied after cropping. If `--max-size` is also specified, resizing is applied after cropping.
## Display
If several displays are available on the Android device, it is possible to
select the display to mirror:
```bash
scrcpy --display=1
```
The list of display ids can be retrieved by:
```bash
scrcpy --list-displays
```
A secondary display may only be controlled if the device runs at least Android
10 (otherwise it is mirrored as read-only).
## Buffering ## Buffering
By default, there is no video buffering, to get the lowest possible latency. By default, there is no video buffering, to get the lowest possible latency.

View File

@@ -3,12 +3,6 @@ package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.DisplayManager; import com.genymobile.scrcpy.wrappers.DisplayManager;
import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.MediaCodec;
import java.util.List; import java.util.List;
public final class LogUtils { public final class LogUtils {
@@ -66,48 +60,4 @@ public final class LogUtils {
} }
return builder.toString(); return builder.toString();
} }
private static String getCameraFacingName(int facing) {
switch (facing) {
case CameraCharacteristics.LENS_FACING_FRONT:
return "front";
case CameraCharacteristics.LENS_FACING_BACK:
return "back";
case CameraCharacteristics.LENS_FACING_EXTERNAL:
return "external";
default:
return "unknown";
}
}
public static String buildCameraListMessage() {
StringBuilder builder = new StringBuilder("List of cameras:");
CameraManager cameraManager = ServiceManager.getCameraManager();
try {
String[] cameraIds = cameraManager.getCameraIdList();
if (cameraIds == null || cameraIds.length == 0) {
builder.append("\n (none)");
} else {
for (String id : cameraIds) {
builder.append("\n --video-source=camera --camera=").append(id);
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
Integer facingInteger = characteristics.get(CameraCharacteristics.LENS_FACING);
if (facingInteger != null) {
int facing = facingInteger;
builder.append(" (").append(getCameraFacingName(facing)).append(')');
}
StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
android.util.Size[] sizes = configs.getOutputSizes(MediaCodec.class);
for (android.util.Size size : sizes) {
// TODO remove (just for testing)
builder.append("\n - " + size.getWidth() + "x" + size.getHeight());
}
}
}
} catch (CameraAccessException e) {
builder.append("\n (access denied)");
}
return builder.toString();
}
} }

View File

@@ -38,7 +38,6 @@ public class Options {
private boolean listEncoders; private boolean listEncoders;
private boolean listDisplays; private boolean listDisplays;
private boolean listCameras;
// Options not used by the scrcpy client, but useful to use scrcpy-server directly // Options not used by the scrcpy client, but useful to use scrcpy-server directly
private boolean sendDeviceMeta = true; // send device name and size private boolean sendDeviceMeta = true; // send device name and size
@@ -162,10 +161,6 @@ public class Options {
return listDisplays; return listDisplays;
} }
public boolean getListCameras() {
return listCameras;
}
public boolean getSendDeviceMeta() { public boolean getSendDeviceMeta() {
return sendDeviceMeta; return sendDeviceMeta;
} }
@@ -311,9 +306,6 @@ public class Options {
case "list_displays": case "list_displays":
options.listDisplays = Boolean.parseBoolean(value); options.listDisplays = Boolean.parseBoolean(value);
break; break;
case "list_cameras":
options.listCameras = Boolean.parseBoolean(value);
break;
case "send_device_meta": case "send_device_meta":
options.sendDeviceMeta = Boolean.parseBoolean(value); options.sendDeviceMeta = Boolean.parseBoolean(value);
break; break;

View File

@@ -1,83 +0,0 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.SurfaceControl;
import android.graphics.Rect;
import android.os.Build;
import android.os.IBinder;
import android.view.Surface;
public class ScreenCapture extends SurfaceCapture implements Device.RotationListener, Device.FoldListener {
private final Device device;
private IBinder display;
public ScreenCapture(Device device) {
this.device = device;
}
@Override
public void init() {
display = createDisplay();
device.setRotationListener(this);
device.setFoldListener(this);
}
@Override
public void release() {
device.setRotationListener(null);
device.setFoldListener(null);
SurfaceControl.destroyDisplay(display);
}
@Override
public Size getSize() {
return device.getScreenInfo().getVideoSize();
}
@Override
public void setMaxSize(int size) {
device.setMaxSize(size);
}
@Override
public void setSurface(Surface surface) {
ScreenInfo screenInfo = device.getScreenInfo();
Rect contentRect = screenInfo.getContentRect();
// does not include the locked video orientation
Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
int videoRotation = screenInfo.getVideoRotation();
int layerStack = device.getLayerStack();
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
}
@Override
public void onFoldChanged(int displayId, boolean folded) {
requestReset();
}
@Override
public void onRotationChanged(int rotation) {
requestReset();
}
private static IBinder createDisplay() {
// Since Android 12 (preview), secure displays could not be created with shell permissions anymore.
// On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S".
boolean secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S".equals(
Build.VERSION.CODENAME));
return SurfaceControl.createDisplay("scrcpy", secure);
}
private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect, int layerStack) {
SurfaceControl.openTransaction();
try {
SurfaceControl.setDisplaySurface(display, surface);
SurfaceControl.setDisplayProjection(display, orientation, deviceRect, displayRect);
SurfaceControl.setDisplayLayerStack(display, layerStack);
} finally {
SurfaceControl.closeTransaction();
}
}
}

View File

@@ -17,7 +17,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 SurfaceEncoder implements AsyncProcessor { public class ScreenEncoder implements Device.RotationListener, Device.FoldListener, 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
@@ -27,7 +27,9 @@ public class SurfaceEncoder implements AsyncProcessor {
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 SurfaceCapture capture; private final AtomicBoolean resetCapture = new AtomicBoolean();
private final Device device;
private final Streamer streamer; private final Streamer streamer;
private final String encoderName; private final String encoderName;
private final List<CodecOption> codecOptions; private final List<CodecOption> codecOptions;
@@ -41,9 +43,9 @@ public class SurfaceEncoder implements AsyncProcessor {
private Thread thread; private Thread thread;
private final AtomicBoolean stopped = new AtomicBoolean(); private final AtomicBoolean stopped = new AtomicBoolean();
public SurfaceEncoder(SurfaceCapture capture, Streamer streamer, int videoBitRate, int maxFps, List<CodecOption> codecOptions, String encoderName, public ScreenEncoder(Device device, Streamer streamer, int videoBitRate, int maxFps, List<CodecOption> codecOptions, String encoderName,
boolean downsizeOnError) { boolean downsizeOnError) {
this.capture = capture; this.device = device;
this.streamer = streamer; this.streamer = streamer;
this.videoBitRate = videoBitRate; this.videoBitRate = videoBitRate;
this.maxFps = maxFps; this.maxFps = maxFps;
@@ -52,29 +54,51 @@ public class SurfaceEncoder implements AsyncProcessor {
this.downsizeOnError = downsizeOnError; this.downsizeOnError = downsizeOnError;
} }
@Override
public void onFoldChanged(int displayId, boolean folded) {
resetCapture.set(true);
}
@Override
public void onRotationChanged(int rotation) {
resetCapture.set(true);
}
private boolean consumeResetCapture() {
return resetCapture.getAndSet(false);
}
private void streamScreen() throws IOException, ConfigurationException { private void streamScreen() throws IOException, ConfigurationException {
Codec codec = streamer.getCodec(); Codec codec = streamer.getCodec();
MediaCodec mediaCodec = createMediaCodec(codec, encoderName); MediaCodec mediaCodec = createMediaCodec(codec, encoderName);
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions); MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
IBinder display = createDisplay();
device.setRotationListener(this);
device.setFoldListener(this);
capture.init(); streamer.writeVideoHeader(device.getScreenInfo().getVideoSize());
try {
streamer.writeVideoHeader(capture.getSize());
boolean alive; boolean alive;
try {
do { do {
Size size = capture.getSize(); ScreenInfo screenInfo = device.getScreenInfo();
format.setInteger(MediaFormat.KEY_WIDTH, size.getWidth()); Rect contentRect = screenInfo.getContentRect();
format.setInteger(MediaFormat.KEY_HEIGHT, size.getHeight());
// include the locked video orientation
Rect videoRect = screenInfo.getVideoSize().toRect();
format.setInteger(MediaFormat.KEY_WIDTH, videoRect.width());
format.setInteger(MediaFormat.KEY_HEIGHT, videoRect.height());
Surface surface = null; Surface surface = null;
try { try {
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
surface = mediaCodec.createInputSurface(); surface = mediaCodec.createInputSurface();
capture.setSurface(surface); // does not include the locked video orientation
Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
int videoRotation = screenInfo.getVideoRotation();
int layerStack = device.getLayerStack();
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
mediaCodec.start(); mediaCodec.start();
@@ -83,7 +107,7 @@ public class SurfaceEncoder implements AsyncProcessor {
mediaCodec.stop(); mediaCodec.stop();
} catch (IllegalStateException | IllegalArgumentException e) { } catch (IllegalStateException | IllegalArgumentException e) {
Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage()); Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage());
if (!prepareRetry(size)) { if (!prepareRetry(device, screenInfo)) {
throw e; throw e;
} }
Ln.i("Retrying..."); Ln.i("Retrying...");
@@ -97,11 +121,13 @@ public class SurfaceEncoder implements AsyncProcessor {
} while (alive); } while (alive);
} finally { } finally {
mediaCodec.release(); mediaCodec.release();
capture.release(); device.setRotationListener(null);
device.setFoldListener(null);
SurfaceControl.destroyDisplay(display);
} }
} }
private boolean prepareRetry(Size currentSize) { private boolean prepareRetry(Device device, ScreenInfo screenInfo) {
if (firstFrameSent) { if (firstFrameSent) {
++consecutiveErrors; ++consecutiveErrors;
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) { if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
@@ -121,7 +147,7 @@ public class SurfaceEncoder implements AsyncProcessor {
// Downsizing on error is only enabled if an encoding failure occurs before the first frame (downsizing later could be surprising) // Downsizing on error is only enabled if an encoding failure occurs before the first frame (downsizing later could be surprising)
int newMaxSize = chooseMaxSizeFallback(currentSize); int newMaxSize = chooseMaxSizeFallback(screenInfo.getVideoSize());
if (newMaxSize == 0) { if (newMaxSize == 0) {
// Must definitively fail // Must definitively fail
return false; return false;
@@ -129,7 +155,7 @@ public class SurfaceEncoder implements AsyncProcessor {
// Retry with a smaller device size // Retry with a smaller device size
Ln.i("Retrying with -m" + newMaxSize + "..."); Ln.i("Retrying with -m" + newMaxSize + "...");
capture.setMaxSize(newMaxSize); device.setMaxSize(newMaxSize);
return true; return true;
} }
@@ -150,14 +176,14 @@ public class SurfaceEncoder implements AsyncProcessor {
boolean alive = true; boolean alive = true;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while (!capture.consumeReset() && !eof) { while (!consumeResetCapture() && !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 (capture.consumeReset()) { if (consumeResetCapture()) {
// must restart encoding with new size // must restart encoding with new size
break; break;
} }
@@ -241,8 +267,8 @@ public class SurfaceEncoder implements AsyncProcessor {
private static IBinder createDisplay() { private static IBinder createDisplay() {
// Since Android 12 (preview), secure displays could not be created with shell permissions anymore. // Since Android 12 (preview), secure displays could not be created with shell permissions anymore.
// On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S". // On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S".
boolean secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S".equals( boolean secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S"
Build.VERSION.CODENAME)); .equals(Build.VERSION.CODENAME));
return SurfaceControl.createDisplay("scrcpy", secure); return SurfaceControl.createDisplay("scrcpy", secure);
} }

View File

@@ -2,7 +2,6 @@ package com.genymobile.scrcpy;
import android.os.BatteryManager; import android.os.BatteryManager;
import android.os.Build; import android.os.Build;
import android.os.Looper;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -100,8 +99,7 @@ public final class Server {
boolean audio = options.getAudio(); boolean audio = options.getAudio();
boolean sendDummyByte = options.getSendDummyByte(); boolean sendDummyByte = options.getSendDummyByte();
boolean camera = true; Workarounds.apply(audio);
Workarounds.apply(audio, camera);
List<AsyncProcessor> asyncProcessors = new ArrayList<>(); List<AsyncProcessor> asyncProcessors = new ArrayList<>();
@@ -134,8 +132,7 @@ public final class Server {
if (video) { if (video) {
Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(), Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(),
options.getSendFrameMeta()); options.getSendFrameMeta());
ScreenCapture screenCapture = new ScreenCapture(device); ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(),
SurfaceEncoder screenEncoder = new SurfaceEncoder(screenCapture, videoStreamer, options.getVideoBitRate(), options.getMaxFps(),
options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError());
asyncProcessors.add(screenEncoder); asyncProcessors.add(screenEncoder);
} }
@@ -182,7 +179,7 @@ public final class Server {
Ln.initLogLevel(options.getLogLevel()); Ln.initLogLevel(options.getLogLevel());
if (options.getListEncoders() || options.getListDisplays() || options.getListCameras()) { if (options.getListEncoders() || options.getListDisplays()) {
if (options.getCleanup()) { if (options.getCleanup()) {
CleanUp.unlinkSelf(); CleanUp.unlinkSelf();
} }
@@ -194,10 +191,6 @@ public final class Server {
if (options.getListDisplays()) { if (options.getListDisplays()) {
Ln.i(LogUtils.buildDisplayListMessage()); Ln.i(LogUtils.buildDisplayListMessage());
} }
if (options.getListCameras()) {
Workarounds.apply(false, true);
Ln.i(LogUtils.buildCameraListMessage());
}
// Just print the requested data, do not mirror // Just print the requested data, do not mirror
return; return;
} }

View File

@@ -1,61 +0,0 @@
package com.genymobile.scrcpy;
import android.view.Surface;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* A video source which can be rendered on a Surface for encoding.
*/
public abstract class SurfaceCapture {
private final AtomicBoolean resetCapture = new AtomicBoolean();
/**
* Request the encoding session to be restarted, for example if the capture implementation detects that the video source size has changed (on
* device rotation for example).
*/
protected void requestReset() {
resetCapture.set(true);
}
/**
* Consume the reset request (intended to be called by the encoder).
*
* @return {@code true} if a reset request was pending, {@code false} otherwise.
*/
public boolean consumeReset() {
return resetCapture.getAndSet(false);
}
/**
* Called once before the capture starts.
*/
public abstract void init();
/**
* Called after the capture ends (if and only if {@link #init()} has been called).
*/
public abstract void release();
/**
* Return the video size
*
* @return the video size
*/
public abstract Size getSize();
/**
* Set the maximum capture size (set by the encoder if it does not support the current size).
*
* @param size Maximum size
*/
public abstract void setMaxSize(int size);
/**
* Set the target surface.
*
* @param surface the surface which will be encoded
*/
public abstract void setSurface(Surface surface);
}

View File

@@ -28,13 +28,14 @@ public final class Workarounds {
// not instantiable // not instantiable
} }
public static void apply(boolean audio, boolean camera) { public static void apply(boolean audio) {
Workarounds.prepareMainLooper(); Workarounds.prepareMainLooper();
boolean mustFillAppInfo = false; boolean mustFillAppInfo = false;
boolean mustFillBaseContext = false; boolean mustFillBaseContext = false;
boolean mustFillAppContext = false; boolean mustFillAppContext = false;
if (Build.BRAND.equalsIgnoreCase("meizu")) { if (Build.BRAND.equalsIgnoreCase("meizu")) {
// Workarounds must be applied for Meizu phones: // Workarounds must be applied for Meizu phones:
// - <https://github.com/Genymobile/scrcpy/issues/240> // - <https://github.com/Genymobile/scrcpy/issues/240>
@@ -64,10 +65,6 @@ public final class Workarounds {
mustFillAppContext = true; mustFillAppContext = true;
} }
if (camera) {
mustFillAppInfo = true;
}
if (mustFillAppInfo) { if (mustFillAppInfo) {
Workarounds.fillAppInfo(); Workarounds.fillAppInfo();
} }
@@ -157,7 +154,7 @@ public final class Workarounds {
} }
} }
private static void fillBaseContext() { public static void fillBaseContext() {
try { try {
fillActivityThread(); fillActivityThread();

View File

@@ -17,7 +17,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@SuppressLint("PrivateApi,DiscouragedPrivateApi") @SuppressLint("PrivateApi,DiscouragedPrivateApi")
public final class ActivityManager { public class ActivityManager {
private final IInterface manager; private final IInterface manager;
private Method getContentProviderExternalMethod; private Method getContentProviderExternalMethod;

View File

@@ -11,7 +11,7 @@ import android.os.IInterface;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
public final class ClipboardManager { public class ClipboardManager {
private final IInterface manager; private final IInterface manager;
private Method getPrimaryClipMethod; private Method getPrimaryClipMethod;
private Method setPrimaryClipMethod; private Method setPrimaryClipMethod;

View File

@@ -14,7 +14,7 @@ import java.io.Closeable;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
public final class ContentProvider implements Closeable { public class ContentProvider implements Closeable {
public static final String TABLE_SYSTEM = "system"; public static final String TABLE_SYSTEM = "system";
public static final String TABLE_SECURE = "secure"; public static final String TABLE_SECURE = "secure";

View File

@@ -1,15 +1,9 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.FakeContext;
import com.genymobile.scrcpy.Workarounds;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context;
import android.hardware.camera2.CameraManager;
import android.os.IBinder; import android.os.IBinder;
import android.os.IInterface; import android.os.IInterface;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@@ -32,7 +26,6 @@ public final class ServiceManager {
private static StatusBarManager statusBarManager; private static StatusBarManager statusBarManager;
private static ClipboardManager clipboardManager; private static ClipboardManager clipboardManager;
private static ActivityManager activityManager; private static ActivityManager activityManager;
private static CameraManager cameraManager;
private ServiceManager() { private ServiceManager() {
/* not instantiable */ /* not instantiable */
@@ -136,16 +129,4 @@ public final class ServiceManager {
return activityManager; return activityManager;
} }
public static CameraManager getCameraManager() {
if (cameraManager == null) {
try {
Constructor<CameraManager> ctor = CameraManager.class.getDeclaredConstructor(Context.class);
cameraManager = ctor.newInstance(FakeContext.get());
} catch (Exception e) {
throw new AssertionError(e);
}
}
return cameraManager;
}
} }

View File

@@ -7,7 +7,7 @@ import android.os.IInterface;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
public final class StatusBarManager { public class StatusBarManager {
private final IInterface manager; private final IInterface manager;
private Method expandNotificationsPanelMethod; private Method expandNotificationsPanelMethod;