Compare commits

..

7 Commits

Author SHA1 Message Date
fdc58722b3 Adapt to display API changes
The method SurfaceControl.createDisplay() has been removed in AOSP.

Use DisplayManager to create a VirtualDisplay object instead.

Fixes #4646 <https://github.com/Genymobile/scrcpy/issues/4646>
Fixes #4656 <https://github.com/Genymobile/scrcpy/issues/4656>
PR #4657 <https://github.com/Genymobile/scrcpy/pull/4657>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-02-09 18:31:20 +01:00
5a6b8310ca Add note about official website 2023-12-13 12:55:14 +01:00
c6ff78f414 Update links to v2.3.1 2023-12-02 12:39:05 +01:00
40f2560d98 Bump version to 2.3.1 2023-12-02 12:30:19 +01:00
26aa28c998 Merge branch 'master' into release 2023-12-02 12:29:31 +01:00
ef79fcbbd2 Fix AV1 demuxing
For AV1, the config packet must not be merged with the next non-config
packet.

This fixes the following error when passing --video-codec=av1:

> INFO: [FFmpeg] libdav1d 1.3.0
> ERROR: [FFmpeg] Unknown OBU type 0 of size 29393
> ERROR: [FFmpeg] Error parsing OBU data
> ERROR: Decoder 'video': could not send video packet: -1094995529

PR #4487 <https://github.com/Genymobile/scrcpy/pull/4487>
2023-12-02 12:20:01 +01:00
bd9292931e Mention exclusive_caps mode in v4l2 documentation
PR #4435 <https://github.com/Genymobile/scrcpy/pull/4435>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-11-28 08:32:28 +01:00
17 changed files with 219 additions and 102 deletions

View File

@ -1,4 +1,8 @@
# scrcpy (v2.3)
**This GitHub repo (<https://github.com/Genymobile/scrcpy>) is the only official
source for the project. Do not download releases from random websites, even if
their name contains `scrcpy`.**
# scrcpy (v2.3.1)
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />

View File

@ -13,7 +13,7 @@ BEGIN
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
VALUE "OriginalFilename", "scrcpy.exe"
VALUE "ProductName", "scrcpy"
VALUE "ProductVersion", "2.3"
VALUE "ProductVersion", "2.3.1"
END
END
BLOCK "VarFileInfo"

View File

@ -227,8 +227,9 @@ run_demuxer(void *data) {
}
// Config packets must be merged with the next non-config packet only for
// video streams
bool must_merge_config_packet = codec->type == AVMEDIA_TYPE_VIDEO;
// H.26x
bool must_merge_config_packet = raw_codec_id == SC_CODEC_ID_H264
|| raw_codec_id == SC_CODEC_ID_H265;
struct sc_packet_merger merger;

View File

@ -233,10 +233,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server
- [`scrcpy-server-v2.3`][direct-scrcpy-server]
<sub>SHA-256: `8daed514d7796fca6987dc973e201bd15ba51d0f7258973dec92d9ded00dbd5f`</sub>
- [`scrcpy-server-v2.3.1`][direct-scrcpy-server]
<sub>SHA-256: `f6814822fc308a7a532f253485c9038183c6296a6c5df470a9e383b4f8e7605b`</sub>
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-server-v2.3
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-server-v2.3.1
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:

View File

@ -21,6 +21,13 @@ This will create a new video device in `/dev/videoN`, where `N` is an integer
(more [options](https://github.com/umlaeute/v4l2loopback#options) are available
to create several devices or devices with specific IDs).
If you encounter problems detecting your device with Chrome/WebRTC, you can try
`exclusive_caps` mode:
```
sudo modprobe v4l2loopback exclusive_caps=1
```
To list the enabled devices:
```bash

View File

@ -4,14 +4,14 @@
Download the [latest release]:
- [`scrcpy-win64-v2.3.zip`][direct-win64] (64-bit)
<sub>SHA-256: `a2fdd2733bd337261bb493e77d990078a23e7a40149dd0c0dc45725c929a715f`</sub>
- [`scrcpy-win32-v2.3.zip`][direct-win32] (32-bit)
<sub>SHA-256: `dfdbb69a872d717aed5bcfe352e571564c357fdb7a9c172d69f450fdf5154a0a`</sub>
- [`scrcpy-win64-v2.3.1.zip`][direct-win64] (64-bit)
<sub>SHA-256: `f1f78ac98214078425804e524a1bed515b9d4b8a05b78d210a4ced2b910b262d`</sub>
- [`scrcpy-win32-v2.3.1.zip`][direct-win32] (32-bit)
<sub>SHA-256: `5dffc2d432e9b8b5b0e16f12e71428c37c70d9124cfbe7620df0b41b7efe91ff`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-win64-v2.3.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-win32-v2.3.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-win64-v2.3.1.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-win32-v2.3.1.zip
and extract it.

View File

@ -2,8 +2,8 @@
set -e
BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-server-v2.3
PREBUILT_SERVER_SHA256=8daed514d7796fca6987dc973e201bd15ba51d0f7258973dec92d9ded00dbd5f
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-server-v2.3.1
PREBUILT_SERVER_SHA256=f6814822fc308a7a532f253485c9038183c6296a6c5df470a9e383b4f8e7605b
echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

@ -1,5 +1,5 @@
project('scrcpy', 'c',
version: '2.3',
version: '2.3.1',
meson_version: '>= 0.48',
default_options: [
'c_std=c11',

View File

@ -7,8 +7,8 @@ android {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 34
versionCode 20300
versionName "2.3"
versionCode 20301
versionName "2.3.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@ -12,7 +12,7 @@
set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=2.3
SCRCPY_VERSION_NAME=2.3.1
PLATFORM=${ANDROID_PLATFORM:-34}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0}

View File

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

View File

@ -0,0 +1,25 @@
/* //device/java/android/android/hardware/ISensorListener.aidl
**
** Copyright 2008, 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}
*/
interface IRotationWatcher {
oneway void onRotationChanged(int rotation);
}

View File

@ -2,7 +2,6 @@ package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ClipboardManager;
import com.genymobile.scrcpy.wrappers.DisplayControl;
import com.genymobile.scrcpy.wrappers.DisplayManager;
import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import com.genymobile.scrcpy.wrappers.SurfaceControl;
@ -11,10 +10,10 @@ import com.genymobile.scrcpy.wrappers.WindowManager;
import android.content.IOnPrimaryClipChangedListener;
import android.graphics.Rect;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.SystemClock;
import android.view.IDisplayFoldListener;
import android.view.IRotationWatcher;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyCharacterMap;
@ -34,21 +33,26 @@ public final class Device {
public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1;
public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
public interface DisplayChangeListener {
void onDisplayChanged();
public interface RotationListener {
void onRotationChanged(int rotation);
}
public interface FoldListener {
void onFoldChanged(int displayId, boolean folded);
}
public interface ClipboardListener {
void onClipboardTextChanged(String text);
}
private final Size deviceSize;
private final Rect crop;
private int maxSize;
private final int lockVideoOrientation;
private Size deviceSize;
private ScreenInfo screenInfo;
private DisplayChangeListener displayChangeListener;
private RotationListener rotationListener;
private FoldListener foldListener;
private ClipboardListener clipboardListener;
private final AtomicBoolean isSettingClipboard = new AtomicBoolean();
@ -82,26 +86,46 @@ public final class Device {
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
layerStack = displayInfo.getLayerStack();
HandlerThread displayListenerThread = new HandlerThread("DisplayListenerThread");
displayListenerThread.start();
Handler displayListenerHandler = new Handler(displayListenerThread.getLooper());
ServiceManager.getDisplayManager().registerDisplayListener(new DisplayManager.DisplayListener() {
ServiceManager.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
@Override
public void onDisplayChanged(int displayId) {
if (Device.this.displayId != displayId) {
return;
}
public void onRotationChanged(int rotation) {
synchronized (Device.this) {
screenInfo = screenInfo.withDeviceRotation(rotation);
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
deviceSize = displayInfo.getSize();
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
if (displayChangeListener != null) {
displayChangeListener.onDisplayChanged();
// notify
if (rotationListener != null) {
rotationListener.onRotationChanged(rotation);
}
}
}
}, displayListenerHandler);
}, displayId);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ServiceManager.getWindowManager().registerDisplayFoldListener(new IDisplayFoldListener.Stub() {
@Override
public void onDisplayFoldChanged(int displayId, boolean folded) {
if (Device.this.displayId != displayId) {
// Ignore events related to other display ids
return;
}
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 control and autosync are enabled, synchronize Android clipboard to the computer automatically
@ -140,6 +164,10 @@ public final class Device {
}
}
public int getDisplayId() {
return displayId;
}
public synchronized void setMaxSize(int newMaxSize) {
maxSize = newMaxSize;
screenInfo = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation);
@ -230,8 +258,12 @@ public final class Device {
return ServiceManager.getPowerManager().isScreenOn();
}
public synchronized void setDisplayChangeListener(DisplayChangeListener displayChangeListener) {
this.displayChangeListener = displayChangeListener;
public synchronized void setRotationListener(RotationListener rotationListener) {
this.rotationListener = rotationListener;
}
public synchronized void setFoldListener(FoldListener foldlistener) {
this.foldListener = foldlistener;
}
public synchronized void setClipboardListener(ClipboardListener clipboardListener) {

View File

@ -1,16 +1,19 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import com.genymobile.scrcpy.wrappers.SurfaceControl;
import android.graphics.Rect;
import android.hardware.display.VirtualDisplay;
import android.os.Build;
import android.os.IBinder;
import android.view.Surface;
public class ScreenCapture extends SurfaceCapture implements Device.DisplayChangeListener {
public class ScreenCapture extends SurfaceCapture implements Device.RotationListener, Device.FoldListener {
private final Device device;
private IBinder display;
private VirtualDisplay virtualDisplay;
public ScreenCapture(Device device) {
this.device = device;
@ -18,7 +21,8 @@ public class ScreenCapture extends SurfaceCapture implements Device.DisplayChang
@Override
public void init() {
device.setDisplayChangeListener(this);
device.setRotationListener(this);
device.setFoldListener(this);
}
@Override
@ -33,14 +37,35 @@ public class ScreenCapture extends SurfaceCapture implements Device.DisplayChang
if (display != null) {
SurfaceControl.destroyDisplay(display);
display = null;
}
if (virtualDisplay != null) {
virtualDisplay.release();
virtualDisplay = null;
}
try {
display = createDisplay();
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
Ln.d("Display: using SurfaceControl API");
} catch (Exception surfaceControlException) {
Rect videoRect = screenInfo.getVideoSize().toRect();
try {
virtualDisplay = ServiceManager.getDisplayManager()
.createVirtualDisplay("scrcpy", videoRect.width(), videoRect.height(), device.getDisplayId(), surface);
Ln.d("Display: using DisplayManager API");
} catch (Exception displayManagerException) {
Ln.e("Could not create display using SurfaceControl", surfaceControlException);
Ln.e("Could not create display using DisplayManager", displayManagerException);
throw new AssertionError("Could not create display");
}
}
display = createDisplay();
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
}
@Override
public void release() {
device.setDisplayChangeListener(null);
device.setRotationListener(null);
device.setFoldListener(null);
if (display != null) {
SurfaceControl.destroyDisplay(display);
}
@ -58,11 +83,16 @@ public class ScreenCapture extends SurfaceCapture implements Device.DisplayChang
}
@Override
public void onDisplayChanged() {
public void onFoldChanged(int displayId, boolean folded) {
requestReset();
}
private static IBinder createDisplay() {
@Override
public void onRotationChanged(int rotation) {
requestReset();
}
private static IBinder createDisplay() throws Exception {
// 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(

View File

@ -5,21 +5,18 @@ import com.genymobile.scrcpy.DisplayInfo;
import com.genymobile.scrcpy.Ln;
import com.genymobile.scrcpy.Size;
import android.os.Build;
import android.os.Handler;
import android.hardware.display.VirtualDisplay;
import android.view.Display;
import android.view.Surface;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class DisplayManager {
// Constant copied from AOSP framework_base: core/java/android/hardware/display/DisplayManager.java
private static final long EVENT_FLAG_DISPLAY_CHANGED = 1L << 2;
private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal
private Method createVirtualDisplayMethod;
public DisplayManager(Object manager) {
this.manager = manager;
@ -102,46 +99,16 @@ public final class DisplayManager {
}
}
public void registerDisplayListener(DisplayListener listener, Handler handler) {
try {
Class<?> displayListenerClass = Class.forName("android.hardware.display.DisplayManager$DisplayListener");
Object displayListenerProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{displayListenerClass},
(proxy, method, args) -> {
if ("onDisplayChanged".equals(method.getName())) {
listener.onDisplayChanged((int) args[0]);
}
return null;
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
try {
manager.getClass()
.getMethod("registerDisplayListener", displayListenerClass, Handler.class, long.class)
.invoke(manager, displayListenerProxy, handler, EVENT_FLAG_DISPLAY_CHANGED);
return;
} catch (NoSuchMethodException e) {
// fall-through
}
}
manager.getClass()
.getMethod("registerDisplayListener", displayListenerClass, Handler.class)
.invoke(manager, displayListenerProxy, handler);
} catch (Exception e) {
// Screen size won't be updated, not a fatal error
Ln.e("Could not register display listener", e);
private Method getCreateVirtualDisplayMethod() throws NoSuchMethodException {
if (createVirtualDisplayMethod == null) {
createVirtualDisplayMethod = android.hardware.display.DisplayManager.class
.getMethod("createVirtualDisplay", String.class, int.class, int.class, int.class, Surface.class);
}
return createVirtualDisplayMethod;
}
// Partially copied from AOSP framework_base: core/java/android/hardware/display/DisplayManager.java
public interface DisplayListener {
/**
* Called whenever the properties of a logical {@link android.view.Display},
* such as size and density, have changed.
*
* @param displayId The id of the logical display that changed.
*/
void onDisplayChanged(int displayId);
public VirtualDisplay createVirtualDisplay(String name, int width, int height, int displayIdToMirror, Surface surface) throws Exception {
Method method = getCreateVirtualDisplayMethod();
return (VirtualDisplay) method.invoke(null, name, width, height, displayIdToMirror, surface);
}
}

View File

@ -78,12 +78,8 @@ public final class SurfaceControl {
}
}
public static IBinder createDisplay(String name, boolean secure) {
try {
return (IBinder) CLASS.getMethod("createDisplay", String.class, boolean.class).invoke(null, name, secure);
} catch (Exception e) {
throw new AssertionError(e);
}
public static IBinder createDisplay(String name, boolean secure) throws Exception {
return (IBinder) CLASS.getMethod("createDisplay", String.class, boolean.class).invoke(null, name, secure);
}
private static Method getGetBuiltInDisplayMethod() throws NoSuchMethodException {

View File

@ -2,7 +2,10 @@ package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln;
import android.annotation.TargetApi;
import android.os.IInterface;
import android.view.IDisplayFoldListener;
import android.view.IRotationWatcher;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@ -91,4 +94,30 @@ public final class WindowManager {
Ln.e("Could not invoke method", e);
}
}
public void registerRotationWatcher(IRotationWatcher rotationWatcher, int displayId) {
try {
Class<?> cls = manager.getClass();
try {
// display parameter added since this commit:
// https://android.googlesource.com/platform/frameworks/base/+/35fa3c26adcb5f6577849fd0df5228b1f67cf2c6%5E%21/#F1
cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, displayId);
} catch (NoSuchMethodException e) {
// old version
cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher);
}
} catch (Exception e) {
Ln.e("Could not register rotation watcher", e);
}
}
@TargetApi(29)
public void registerDisplayFoldListener(IDisplayFoldListener foldListener) {
try {
Class<?> cls = manager.getClass();
cls.getMethod("registerDisplayFoldListener", IDisplayFoldListener.class).invoke(manager, foldListener);
} catch (Exception e) {
Ln.e("Could not register display fold listener", e);
}
}
}