Compare commits
15 Commits
camera.37
...
camerafpsr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dedbc3f8cd | ||
|
|
ad8ced5b1e | ||
|
|
36b17ee068 | ||
|
|
4e4ddc499f | ||
|
|
8d76b3e06d | ||
|
|
85a0b935c9 | ||
|
|
8c3e2bae7b | ||
|
|
446ea818a4 | ||
|
|
c3c7bf7af3 | ||
|
|
5000368c2f | ||
|
|
855ae4adb1 | ||
|
|
a8db3ec9e2 | ||
|
|
ff579990c2 | ||
|
|
b8c5853aa6 | ||
|
|
90ca46ee41 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ build/
|
||||
.gradle/
|
||||
/x/
|
||||
local.properties
|
||||
/scrcpy-server
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# scrcpy (v2.1.1)
|
||||
# scrcpy (v2.2)
|
||||
|
||||
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ cd "$DIR"
|
||||
mkdir -p "$PREBUILT_DATA_DIR"
|
||||
cd "$PREBUILT_DATA_DIR"
|
||||
|
||||
DEP_DIR=platform-tools-34.0.3
|
||||
DEP_DIR=platform-tools-34.0.5
|
||||
|
||||
FILENAME=platform-tools_r34.0.3-windows.zip
|
||||
SHA256SUM=fce992e93eb786fc9f47df93d83a7b912c46742d45c39d712c02e06d05b72e2b
|
||||
FILENAME=platform-tools_r34.0.5-windows.zip
|
||||
SHA256SUM=3f8320152704377de150418a3c4c9d07d16d80a6c0d0d8f7289c22c499e33571
|
||||
|
||||
if [[ -d "$DEP_DIR" ]]
|
||||
then
|
||||
|
||||
@@ -6,10 +6,10 @@ cd "$DIR"
|
||||
mkdir -p "$PREBUILT_DATA_DIR"
|
||||
cd "$PREBUILT_DATA_DIR"
|
||||
|
||||
DEP_DIR=SDL2-2.28.0
|
||||
DEP_DIR=SDL2-2.28.4
|
||||
|
||||
FILENAME=SDL2-devel-2.28.0-mingw.tar.gz
|
||||
SHA256SUM=b91ce59eeacd4a9db403f976fd2337d9360b21ada374124417d716065c380e20
|
||||
FILENAME=SDL2-devel-2.28.4-mingw.tar.gz
|
||||
SHA256SUM=779d091072cf97291f80030f5232d97aa3d48ab0f2c14fe0b9d9a33c593cdc35
|
||||
|
||||
if [[ -d "$DEP_DIR" ]]
|
||||
then
|
||||
|
||||
@@ -13,7 +13,7 @@ BEGIN
|
||||
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
|
||||
VALUE "OriginalFilename", "scrcpy.exe"
|
||||
VALUE "ProductName", "scrcpy"
|
||||
VALUE "ProductVersion", "2.1.1"
|
||||
VALUE "ProductVersion", "v2.2"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -17,5 +17,5 @@ endian = 'little'
|
||||
|
||||
[properties]
|
||||
prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-4/win32'
|
||||
prebuilt_sdl2 = 'SDL2-2.28.0/i686-w64-mingw32'
|
||||
prebuilt_sdl2 = 'SDL2-2.28.4/i686-w64-mingw32'
|
||||
prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32'
|
||||
|
||||
@@ -17,5 +17,5 @@ endian = 'little'
|
||||
|
||||
[properties]
|
||||
prebuilt_ffmpeg = 'ffmpeg-6.0-scrcpy-4/win64'
|
||||
prebuilt_sdl2 = 'SDL2-2.28.0/x86_64-w64-mingw32'
|
||||
prebuilt_sdl2 = 'SDL2-2.28.4/x86_64-w64-mingw32'
|
||||
prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64'
|
||||
|
||||
@@ -233,10 +233,10 @@ install` must be run as root)._
|
||||
|
||||
#### Option 2: Use prebuilt server
|
||||
|
||||
- [`scrcpy-server-v2.1.1`][direct-scrcpy-server]
|
||||
<sub>SHA-256: `9558db6c56743a1dc03b38f59801fb40e91cc891f8fc0c89e5b0b067761f148e`</sub>
|
||||
- [`scrcpy-server-v2.2`][direct-scrcpy-server]
|
||||
<sub>SHA-256: `c85c4aa84305efb69115cd497a120ebdd10258993b4cf123a8245b3d99d49874`</sub>
|
||||
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-server-v2.1.1
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-server-v2.2
|
||||
|
||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||
configuration:
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
|
||||
Download the [latest release]:
|
||||
|
||||
- [`scrcpy-win64-v2.1.1.zip`][direct-win64] (64-bit)
|
||||
<sub>SHA-256: `f77281e1bce2f9934617699c581f063d5b327f012eff602ee98fb2ef550c25c2`</sub>
|
||||
- [`scrcpy-win32-v2.1.1.zip`][direct-win32] (32-bit)
|
||||
<sub>SHA-256: `ef7ae7fbe9449f2643febdc2244fb186d1a746a3c736394150cfd14f06d3c943`</sub>
|
||||
- [`scrcpy-win64-v2.2.zip`][direct-win64] (64-bit)
|
||||
<sub>SHA-256: `9f9da88ac4c8319dcb9bf852f2d9bba942bac663413383419cddf64eaa5685bd`</sub>
|
||||
- [`scrcpy-win32-v2.2.zip`][direct-win32] (32-bit)
|
||||
<sub>SHA-256: `cb84269fc847b8b880e320879492a1ae6c017b42175f03e199530f7a53be9d74`</sub>
|
||||
|
||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-win64-v2.1.1.zip
|
||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-win32-v2.1.1.zip
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-win64-v2.2.zip
|
||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-win32-v2.2.zip
|
||||
|
||||
and extract it.
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
set -e
|
||||
|
||||
BUILDDIR=build-auto
|
||||
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-server-v2.1.1
|
||||
PREBUILT_SERVER_SHA256=9558db6c56743a1dc03b38f59801fb40e91cc891f8fc0c89e5b0b067761f148e
|
||||
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-server-v2.2
|
||||
PREBUILT_SERVER_SHA256=c85c4aa84305efb69115cd497a120ebdd10258993b4cf123a8245b3d99d49874
|
||||
|
||||
echo "[scrcpy] Downloading prebuilt server..."
|
||||
wget "$PREBUILT_SERVER_URL" -O scrcpy-server
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
project('scrcpy', 'c',
|
||||
version: '2.1.1',
|
||||
version: 'v2.2',
|
||||
meson_version: '>= 0.48',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
|
||||
16
release.mk
16
release.mk
@@ -98,10 +98,10 @@ dist-win32: build-server build-win32
|
||||
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/platform-tools-34.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/SDL2-2.28.0/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/SDL2-2.28.4/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/bin/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
|
||||
dist-win64: build-server build-win64
|
||||
@@ -116,10 +116,10 @@ dist-win64: build-server build-win64
|
||||
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/platform-tools-34.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/SDL2-2.28.0/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/SDL2-2.28.4/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/bin/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
|
||||
zip-win32: dist-win32
|
||||
|
||||
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "com.genymobile.scrcpy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionCode 20101
|
||||
versionName "2.1.1"
|
||||
versionCode 200
|
||||
versionName "v2.2"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
set -e
|
||||
|
||||
SCRCPY_DEBUG=false
|
||||
SCRCPY_VERSION_NAME=2.1.1
|
||||
SCRCPY_VERSION_NAME=v2.2
|
||||
|
||||
PLATFORM=${ANDROID_PLATFORM:-33}
|
||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0}
|
||||
|
||||
@@ -132,20 +132,29 @@ public final class DesktopConnection implements Closeable {
|
||||
return controlSocket;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
public void shutdown() throws IOException {
|
||||
if (videoSocket != null) {
|
||||
videoSocket.shutdownInput();
|
||||
videoSocket.shutdownOutput();
|
||||
videoSocket.close();
|
||||
}
|
||||
if (audioSocket != null) {
|
||||
audioSocket.shutdownInput();
|
||||
audioSocket.shutdownOutput();
|
||||
audioSocket.close();
|
||||
}
|
||||
if (controlSocket != null) {
|
||||
controlSocket.shutdownInput();
|
||||
controlSocket.shutdownOutput();
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
if (videoSocket != null) {
|
||||
videoSocket.close();
|
||||
}
|
||||
if (audioSocket != null) {
|
||||
audioSocket.close();
|
||||
}
|
||||
if (controlSocket != null) {
|
||||
controlSocket.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ package com.genymobile.scrcpy;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.AttributionSource;
|
||||
import android.content.MutableContextWrapper;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
|
||||
public final class FakeContext extends MutableContextWrapper {
|
||||
public final class FakeContext extends ContextWrapper {
|
||||
|
||||
public static final String PACKAGE_NAME = "com.android.shell";
|
||||
public static final int ROOT_UID = 0; // Like android.os.Process.ROOT_UID, but before API 29
|
||||
@@ -18,7 +19,7 @@ public final class FakeContext extends MutableContextWrapper {
|
||||
}
|
||||
|
||||
private FakeContext() {
|
||||
super(null);
|
||||
super(Workarounds.getSystemContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,4 +45,9 @@ public final class FakeContext extends MutableContextWrapper {
|
||||
public int getDeviceId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getApplicationContext() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,33 +2,37 @@ package com.genymobile.scrcpy;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
|
||||
/**
|
||||
* Log both to Android logger (so that logs are visible in "adb logcat") and standard output/error (so that they are visible in the terminal
|
||||
* directly).
|
||||
*/
|
||||
public final class Ln {
|
||||
|
||||
public interface ConsolePrinter {
|
||||
void printOut(String message);
|
||||
void printErr(String message, Throwable throwable);
|
||||
}
|
||||
|
||||
private static final String TAG = "scrcpy";
|
||||
private static final String PREFIX = "[server] ";
|
||||
|
||||
private static final PrintStream CONSOLE_OUT = new PrintStream(new FileOutputStream(FileDescriptor.out));
|
||||
private static final PrintStream CONSOLE_ERR = new PrintStream(new FileOutputStream(FileDescriptor.err));
|
||||
|
||||
enum Level {
|
||||
VERBOSE, DEBUG, INFO, WARN, ERROR
|
||||
}
|
||||
|
||||
private static ConsolePrinter consolePrinter = new DefaultConsolePrinter();
|
||||
private static Level threshold = Level.INFO;
|
||||
|
||||
private Ln() {
|
||||
// not instantiable
|
||||
}
|
||||
|
||||
public static void setConsolePrinter(ConsolePrinter consolePrinter) {
|
||||
Ln.consolePrinter = consolePrinter;
|
||||
public static void disableSystemStreams() {
|
||||
PrintStream nullStream = new PrintStream(new NullOutputStream());
|
||||
System.setOut(nullStream);
|
||||
System.setErr(nullStream);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,28 +53,31 @@ public final class Ln {
|
||||
public static void v(String message) {
|
||||
if (isEnabled(Level.VERBOSE)) {
|
||||
Log.v(TAG, message);
|
||||
consolePrinter.printOut(PREFIX + "VERBOSE: " + message + '\n');
|
||||
CONSOLE_OUT.print(PREFIX + "VERBOSE: " + message + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
public static void d(String message) {
|
||||
if (isEnabled(Level.DEBUG)) {
|
||||
Log.d(TAG, message);
|
||||
consolePrinter.printOut(PREFIX + "DEBUG: " + message + '\n');
|
||||
CONSOLE_OUT.print(PREFIX + "DEBUG: " + message + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
public static void i(String message) {
|
||||
if (isEnabled(Level.INFO)) {
|
||||
Log.i(TAG, message);
|
||||
consolePrinter.printOut(PREFIX + "INFO: " + message + '\n');
|
||||
CONSOLE_OUT.print(PREFIX + "INFO: " + message + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
public static void w(String message, Throwable throwable) {
|
||||
if (isEnabled(Level.WARN)) {
|
||||
Log.w(TAG, message, throwable);
|
||||
consolePrinter.printErr(PREFIX + "WARN: " + message + '\n', throwable);
|
||||
CONSOLE_ERR.print(PREFIX + "WARN: " + message + '\n');
|
||||
if (throwable != null) {
|
||||
throwable.printStackTrace(CONSOLE_ERR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +88,10 @@ public final class Ln {
|
||||
public static void e(String message, Throwable throwable) {
|
||||
if (isEnabled(Level.ERROR)) {
|
||||
Log.e(TAG, message, throwable);
|
||||
consolePrinter.printErr(PREFIX + "ERROR: " + message + '\n', throwable);
|
||||
CONSOLE_ERR.print(PREFIX + "ERROR: " + message + '\n');
|
||||
if (throwable != null) {
|
||||
throwable.printStackTrace(CONSOLE_ERR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,18 +99,20 @@ public final class Ln {
|
||||
e(message, null);
|
||||
}
|
||||
|
||||
public static class DefaultConsolePrinter implements ConsolePrinter {
|
||||
static class NullOutputStream extends OutputStream {
|
||||
@Override
|
||||
public void printOut(String message) {
|
||||
System.out.print(message);
|
||||
public void write(byte[] b) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void printErr(String message, Throwable throwable) {
|
||||
System.err.print(message);
|
||||
if (throwable != null) {
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
public void write(byte[] b, int off, int len) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,17 @@ package com.genymobile.scrcpy;
|
||||
import com.genymobile.scrcpy.wrappers.DisplayManager;
|
||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.graphics.Rect;
|
||||
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 android.os.Build;
|
||||
import android.util.Range;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
@@ -84,6 +87,7 @@ public final class LogUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
public static String buildCameraListMessage(boolean includeSizes) {
|
||||
StringBuilder builder = new StringBuilder("List of cameras:");
|
||||
CameraManager cameraManager = ServiceManager.getCameraManager();
|
||||
@@ -93,19 +97,35 @@ public final class LogUtils {
|
||||
builder.append("\n (none)");
|
||||
} else {
|
||||
for (String id : cameraIds) {
|
||||
builder.append("\n --video-source=camera --camera-id=").append(id);
|
||||
builder.append("\n --camera-id=").append(id);
|
||||
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
|
||||
|
||||
int facing = characteristics.get(CameraCharacteristics.LENS_FACING);
|
||||
builder.append(" (").append(getCameraFacingName(facing)).append(", ");
|
||||
|
||||
Rect activeSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
|
||||
builder.append(activeSize.width()).append("x").append(activeSize.height()).append(", ");
|
||||
builder.append(activeSize.width()).append("x").append(activeSize.height());
|
||||
|
||||
// Capture frame rates for low-FPS mode are the same for every resolution
|
||||
Range<Integer>[] lowFpsRanges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
|
||||
SortedSet<Integer> uniqueLowFps = getUniqueSet(lowFpsRanges);
|
||||
builder.append("fps=").append(uniqueLowFps).append(')');
|
||||
try {
|
||||
// Capture frame rates for low-FPS mode are the same for every resolution
|
||||
Range<Integer>[] lowFpsRanges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
|
||||
SortedSet<Integer> uniqueLowFps = getUniqueSet(lowFpsRanges);
|
||||
builder.append(", fps=").append(uniqueLowFps);
|
||||
} catch (Exception e) {
|
||||
// Some devices may provide invalid ranges, causing an IllegalArgumentException "lower must be less than or equal to upper"
|
||||
Ln.w("Could not get available frame rates for camera " + id, e);
|
||||
}
|
||||
|
||||
builder.append(')');
|
||||
|
||||
int[] capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
|
||||
if (Arrays.asList(capabilities).contains(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA)) {
|
||||
builder.append("\n Logical multi-camera, backed by physical cameras:");
|
||||
for (String phyId : characteristics.getPhysicalCameraIds()) {
|
||||
CameraCharacteristics phyCharacteristics = cameraManager.getCameraCharacteristics(phyId);
|
||||
appendPhysicalCamera(builder, phyId, phyCharacteristics);
|
||||
}
|
||||
}
|
||||
|
||||
if (includeSizes) {
|
||||
StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||||
@@ -134,6 +154,13 @@ public final class LogUtils {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static void appendPhysicalCamera(StringBuilder builder, String id, CameraCharacteristics characteristics) {
|
||||
builder.append("\n --camera-id=").append(id).append(" (");
|
||||
|
||||
Rect activeSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
|
||||
builder.append(activeSize.width()).append("x").append(activeSize.height()).append(")");
|
||||
}
|
||||
|
||||
private static SortedSet<Integer> getUniqueSet(Range<Integer>[] ranges) {
|
||||
SortedSet<Integer> set = new TreeSet<>();
|
||||
for (Range<Integer> range : ranges) {
|
||||
|
||||
@@ -163,6 +163,8 @@ public final class Server {
|
||||
asyncProcessor.stop();
|
||||
}
|
||||
|
||||
connection.shutdown();
|
||||
|
||||
try {
|
||||
initThread.join();
|
||||
for (AsyncProcessor asyncProcessor : asyncProcessors) {
|
||||
@@ -187,7 +189,7 @@ public final class Server {
|
||||
try {
|
||||
internalMain(args);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
Ln.e(t.getMessage(), t);
|
||||
status = 1;
|
||||
} finally {
|
||||
// By default, the Java process exits when all non-daemon threads are terminated.
|
||||
@@ -204,6 +206,7 @@ public final class Server {
|
||||
|
||||
Options options = Options.parse(args);
|
||||
|
||||
Ln.disableSystemStreams();
|
||||
Ln.initLogLevel(options.getLogLevel());
|
||||
|
||||
Ln.i("Device: [" + Build.MANUFACTURER + "] " + Build.BRAND + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")");
|
||||
|
||||
@@ -14,12 +14,6 @@ import android.os.Build;
|
||||
import android.os.Looper;
|
||||
import android.os.Parcel;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
@@ -27,18 +21,34 @@ import java.lang.reflect.Method;
|
||||
|
||||
public final class Workarounds {
|
||||
|
||||
private static Class<?> activityThreadClass;
|
||||
private static Object activityThread;
|
||||
private static final Class<?> ACTIVITY_THREAD_CLASS;
|
||||
private static final Object ACTIVITY_THREAD;
|
||||
|
||||
static {
|
||||
prepareMainLooper();
|
||||
|
||||
try {
|
||||
// ActivityThread activityThread = new ActivityThread();
|
||||
ACTIVITY_THREAD_CLASS = Class.forName("android.app.ActivityThread");
|
||||
Constructor<?> activityThreadConstructor = ACTIVITY_THREAD_CLASS.getDeclaredConstructor();
|
||||
activityThreadConstructor.setAccessible(true);
|
||||
ACTIVITY_THREAD = activityThreadConstructor.newInstance();
|
||||
|
||||
// ActivityThread.sCurrentActivityThread = activityThread;
|
||||
Field sCurrentActivityThreadField = ACTIVITY_THREAD_CLASS.getDeclaredField("sCurrentActivityThread");
|
||||
sCurrentActivityThreadField.setAccessible(true);
|
||||
sCurrentActivityThreadField.set(null, ACTIVITY_THREAD);
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Workarounds() {
|
||||
// not instantiable
|
||||
}
|
||||
|
||||
public static void apply(boolean audio, boolean camera) {
|
||||
Workarounds.prepareMainLooper();
|
||||
|
||||
boolean mustFillAppInfo = false;
|
||||
boolean mustFillBaseContext = false;
|
||||
boolean mustFillAppContext = false;
|
||||
|
||||
if (Build.BRAND.equalsIgnoreCase("meizu")) {
|
||||
@@ -59,14 +69,7 @@ public final class Workarounds {
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/4015#issuecomment-1595382142>
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/3805#issuecomment-1596148031>
|
||||
mustFillAppInfo = true;
|
||||
mustFillBaseContext = true;
|
||||
mustFillAppContext = true;
|
||||
} else if (Build.MANUFACTURER.equalsIgnoreCase("xiaomi")) {
|
||||
// Trash the messages printed to the console by direct calls to System.out and System.err.
|
||||
// Xiaomi device ROMs may print internal errors using e.printStackTrace(), flooding the console with irrelevant errors.
|
||||
ExclusiveConsolePrinter exclusiveConsolePrinter = new ExclusiveConsolePrinter();
|
||||
exclusiveConsolePrinter.installNullSystemStreams();
|
||||
Ln.setConsolePrinter(new ExclusiveConsolePrinter());
|
||||
}
|
||||
|
||||
if (audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||
@@ -78,15 +81,12 @@ public final class Workarounds {
|
||||
|
||||
if (camera) {
|
||||
mustFillAppInfo = true;
|
||||
mustFillBaseContext = true;
|
||||
mustFillAppContext = true;
|
||||
}
|
||||
|
||||
if (mustFillAppInfo) {
|
||||
Workarounds.fillAppInfo();
|
||||
}
|
||||
if (mustFillBaseContext) {
|
||||
Workarounds.fillBaseContext();
|
||||
}
|
||||
if (mustFillAppContext) {
|
||||
Workarounds.fillAppContext();
|
||||
}
|
||||
@@ -105,27 +105,9 @@ public final class Workarounds {
|
||||
Looper.prepareMainLooper();
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
|
||||
private static void fillActivityThread() throws Exception {
|
||||
if (activityThread == null) {
|
||||
// ActivityThread activityThread = new ActivityThread();
|
||||
activityThreadClass = Class.forName("android.app.ActivityThread");
|
||||
Constructor<?> activityThreadConstructor = activityThreadClass.getDeclaredConstructor();
|
||||
activityThreadConstructor.setAccessible(true);
|
||||
activityThread = activityThreadConstructor.newInstance();
|
||||
|
||||
// ActivityThread.sCurrentActivityThread = activityThread;
|
||||
Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
|
||||
sCurrentActivityThreadField.setAccessible(true);
|
||||
sCurrentActivityThreadField.set(null, activityThread);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
|
||||
private static void fillAppInfo() {
|
||||
try {
|
||||
fillActivityThread();
|
||||
|
||||
// ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData();
|
||||
Class<?> appBindDataClass = Class.forName("android.app.ActivityThread$AppBindData");
|
||||
Constructor<?> appBindDataConstructor = appBindDataClass.getDeclaredConstructor();
|
||||
@@ -141,9 +123,9 @@ public final class Workarounds {
|
||||
appInfoField.set(appBindData, applicationInfo);
|
||||
|
||||
// activityThread.mBoundApplication = appBindData;
|
||||
Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
|
||||
Field mBoundApplicationField = ACTIVITY_THREAD_CLASS.getDeclaredField("mBoundApplication");
|
||||
mBoundApplicationField.setAccessible(true);
|
||||
mBoundApplicationField.set(activityThread, appBindData);
|
||||
mBoundApplicationField.set(ACTIVITY_THREAD, appBindData);
|
||||
} catch (Throwable throwable) {
|
||||
// this is a workaround, so failing is not an error
|
||||
Ln.d("Could not fill app info: " + throwable.getMessage());
|
||||
@@ -153,33 +135,29 @@ public final class Workarounds {
|
||||
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
|
||||
private static void fillAppContext() {
|
||||
try {
|
||||
fillActivityThread();
|
||||
|
||||
Application app = Application.class.newInstance();
|
||||
Application app = new Application();
|
||||
Field baseField = ContextWrapper.class.getDeclaredField("mBase");
|
||||
baseField.setAccessible(true);
|
||||
baseField.set(app, FakeContext.get());
|
||||
|
||||
// activityThread.mInitialApplication = app;
|
||||
Field mInitialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication");
|
||||
Field mInitialApplicationField = ACTIVITY_THREAD_CLASS.getDeclaredField("mInitialApplication");
|
||||
mInitialApplicationField.setAccessible(true);
|
||||
mInitialApplicationField.set(activityThread, app);
|
||||
mInitialApplicationField.set(ACTIVITY_THREAD, app);
|
||||
} catch (Throwable throwable) {
|
||||
// this is a workaround, so failing is not an error
|
||||
Ln.d("Could not fill app context: " + throwable.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void fillBaseContext() {
|
||||
static Context getSystemContext() {
|
||||
try {
|
||||
fillActivityThread();
|
||||
|
||||
Method getSystemContextMethod = activityThreadClass.getDeclaredMethod("getSystemContext");
|
||||
Context context = (Context) getSystemContextMethod.invoke(activityThread);
|
||||
FakeContext.get().setBaseContext(context);
|
||||
Method getSystemContextMethod = ACTIVITY_THREAD_CLASS.getDeclaredMethod("getSystemContext");
|
||||
return (Context) getSystemContextMethod.invoke(ACTIVITY_THREAD);
|
||||
} catch (Throwable throwable) {
|
||||
// this is a workaround, so failing is not an error
|
||||
Ln.d("Could not fill base context: " + throwable.getMessage());
|
||||
Ln.d("Could not get system context: " + throwable.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,48 +296,4 @@ public final class Workarounds {
|
||||
throw new RuntimeException("Cannot create AudioRecord");
|
||||
}
|
||||
}
|
||||
|
||||
static class ExclusiveConsolePrinter implements Ln.ConsolePrinter {
|
||||
|
||||
static class NullOutputStream extends OutputStream {
|
||||
@Override
|
||||
public void write(byte[] b) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private final PrintStream realOut = new PrintStream(new FileOutputStream(FileDescriptor.out));
|
||||
private final PrintStream realErr = new PrintStream(new FileOutputStream(FileDescriptor.err));
|
||||
|
||||
void installNullSystemStreams() {
|
||||
PrintStream nullStream = new PrintStream(new NullOutputStream());
|
||||
System.setOut(nullStream);
|
||||
System.setErr(nullStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void printOut(String message) {
|
||||
realOut.print(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void printErr(String message, Throwable throwable) {
|
||||
realErr.print(message);
|
||||
if (throwable != null) {
|
||||
StringWriter errors = new StringWriter();
|
||||
throwable.printStackTrace(new PrintWriter(errors));
|
||||
realErr.print(errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user