Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to mirror camera #4213

Closed
wants to merge 15 commits into from
Closed

Conversation

yume-chan
Copy link
Contributor

Closes #241

I can imagine this becoming another compatibility hell.

I can only test on emulators and XiaoMi devices, so there isn't much more I can do, for example to the bug report in #241 (comment).

if (cameraManager == null) {
try {
fillBaseContext();
cameraManager = CameraManager.class.getDeclaredConstructor(Context.class)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Android 11 requires correct Context.getOpPackageName(). getSystemContext().getService("camera") doesn't work.

@yume-chan yume-chan changed the title Feat/camera Add option to mirror camera Aug 3, 2023
@yume-chan
Copy link
Contributor Author

I bought a Meizu mblu 10 (Android 11) for testing.

It turns out that, unlike audio capture, camera capture requires the app to keep being at foreground. The capture session stops once the foreground workaround was stopped. That's a pretty huge inconvenience for Android 11 users.

@yume-chan
Copy link
Contributor Author

I know two other methods to become foreground:

  1. Create an overlay window: Shell app doesn't have this permission on Android 11
  2. Foreground service: Shell app doesn't declare the required foreground service type on Android 11 so it can't access cameras.

@rom1v
Copy link
Collaborator

rom1v commented Aug 6, 2023

It turns out that, unlike audio capture, camera capture requires the app to keep being at foreground. The capture session stops once the foreground workaround was stopped. That's a pretty huge inconvenience for Android 11 users.

IMO, it is totally ok to require Android 12+ for the camera feature (the workaround for audio was "simple" and not too intrusive, so it was worth it).

@yume-chan
Copy link
Contributor Author

OK, now I bail out camera mirroring on Android 11 and older, and reverted changes to the foreground workaround.

To be precise, the camera capturing stops when either:

  • com.android.shell app is forcefully stopped (the method we use to stop foreground workaround)
  • The user manually dismisses the heap dump dialog (by tapping any one of three buttons, or tapping outside the dialog), then switches to another app (camera capturing continues to work while the user is staying in the current app)

So, it's barely usable, if the user doesn't use the device while mirroring. However, I have to admit it's too much a restriction.

@chenxiaolong
Copy link
Contributor

Thanks for working on this! It's been working very well for me (Android 13 + Google Pixel 7 Pro).

I've made an additional change in my branch to add support for specifying the camera resolution and frame rate: chenxiaolong@edb8f5b. In particular, this also adds support for the constrained high speed capture mode, allowing 120 and 240 fps camera streams. (This makes scrcpy the only program capable of streaming the camera at a high frame rate on Android 🙂)

@rom1v
Copy link
Collaborator

rom1v commented Sep 3, 2023

Just a small message to say that, despite the lack of feedback for 1 month, the feature from this PR has the highest priority for the next scrcpy version. 😉

I'm just quite busy currently on other subjects at work, which require learning new stuff (which is very interesting, but leaves less time/effort available for scrcpy on my free time).

So it may take some time, but it will be merged 😄 Thank you again for your work 👍

(Recently, I just started to review/rebase/adapt some of your refactor commits, branch camera.2)

@fstecker
Copy link

This is such a cool feature, thanks for making it! I just gave it a try on my Pixel 5a (Android 13) and it works nicely with --camera=1 (the front facing camera) but produces no image when I try --camera=0 (the back facing one). The output is the same in both cases, even with --V verbose. Do you have any idea what the problem could be, or which tests I could do to track it down?

rom1v added a commit that referenced this pull request Oct 31, 2023
Add --camera-high-speed to enable high frame rate camera capture. If
the option is enabled, then --camera-fps is mandatory.

PR #4213 <#4213>

Co-authored-by: Romain Vimont <[email protected]>
Signed-off-by: Andrew Gunnerson <[email protected]>
Signed-off-by: Romain Vimont <[email protected]>
rom1v added a commit that referenced this pull request Oct 31, 2023
@rom1v
Copy link
Collaborator

rom1v commented Oct 31, 2023

Here is a final branch (hopefully), ready to be merged: camera.36.

I added some documentation: bd2da81 | rendered

@rom1v
Copy link
Collaborator

rom1v commented Oct 31, 2023

@yume-chan about Hide warnings on XiaoMi devices (f75f9b9), it seems it does not work for me (tested on your SHA-1 d0d0ece):

$ ./run d --list-cameras
scrcpy 2.1.1 <https://github.com/Genymobile/scrcpy>
INFO: ADB device found:
INFO:     --> (tcpip)  xxx.xxx.xxx.xxx:5555               device  M2101K7AG
DEBUG: Device serial: xxx.xxx.xxx.xxx:5555
DEBUG: Using SCRCPY_SERVER_PATH: d/server/scrcpy-server
d/server/scrcpy-server: 1 file pushed, 0 skipped. 100.2 MB/s (87430 bytes in 0.001s)
java.io.FileNotFoundException: /data/system/theme_config/theme_compatibility.xml: open failed: ENOENT (No such file or directory)
	at libcore.io.IoBridge.open(IoBridge.java:492)
	at java.io.FileInputStream.<init>(FileInputStream.java:160)
	at java.io.FileInputStream.<init>(FileInputStream.java:115)
	at java.io.FileReader.<init>(FileReader.java:58)
	at miui.content.res.ThemeCompatibilityLoader.getVersion(ThemeCompatibilityLoader.java:108)
	at miui.content.res.ThemeCompatibilityLoader.getConfigDocumentTree(ThemeCompatibilityLoader.java:126)
	at miui.content.res.ThemeCompatibilityLoader.loadConfig(ThemeCompatibilityLoader.java:59)
	at miui.content.res.ThemeCompatibility.<clinit>(ThemeCompatibility.java:31)
	at miui.content.res.ThemeCompatibility.isThemeEnabled(ThemeCompatibility.java:111)
	at android.content.res.MiuiResourcesImpl.<clinit>(MiuiResourcesImpl.java:41)
	at android.content.res.Resources.<init>(Resources.java:353)
	at android.content.res.MiuiResources.<init>(MiuiResources.java:49)
	at android.content.res.Resources.getSystem(Resources.java:233)
	at android.app.LoadedApk.<init>(LoadedApk.java:239)
	at android.app.ContextImpl.createSystemContext(ContextImpl.java:2625)
	at android.app.ActivityThread.getSystemContext(ActivityThread.java:2533)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.genymobile.scrcpy.Workarounds.fillBaseContext(Workarounds.java:173)
	at com.genymobile.scrcpy.Workarounds.getCameraManager(Workarounds.java:320)
	at com.genymobile.scrcpy.LogUtils.buildCameraListMessage(LogUtils.java:73)
	at com.genymobile.scrcpy.Server.main(Server.java:203)
	at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
	at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:407)
Caused by: android.system.ErrnoException: open failed: ENOENT (No such file or directory)
	at libcore.io.Linux.open(Native Method)
	at libcore.io.ForwardingOs.open(ForwardingOs.java:166)
	at libcore.io.BlockGuardOs.open(BlockGuardOs.java:254)
	at libcore.io.IoBridge.open(IoBridge.java:478)
	... 22 more
[server] INFO: List of cameras:
    --video-source=camera --camera=0    (Back, 4000x3000)
    --video-source=camera --camera=1    (Front, 4208x3120)

One dirty way to get rid of these messages is:

diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java
index a8e8e36a2..89b800225 100644
--- a/server/src/main/java/com/genymobile/scrcpy/Server.java
+++ b/server/src/main/java/com/genymobile/scrcpy/Server.java
@@ -3,7 +3,9 @@ package com.genymobile.scrcpy;
 import android.os.BatteryManager;
 import android.os.Build;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -222,7 +224,14 @@ public final class Server {
             }
             if (options.getListCameras() || options.getListCameraSizes()) {
                 Workarounds.apply(false, true);
-                Ln.i(LogUtils.buildCameraListMessage(options.getListCameraSizes()));
+                PrintStream out = System.out;
+                PrintStream err = System.err;
+                System.setOut(new PrintStream(new ByteArrayOutputStream()));
+                System.setErr(new PrintStream(new ByteArrayOutputStream()));
+                String s = LogUtils.buildCameraListMessage(options.getListCameraSizes());
+                System.setOut(out);
+                System.setErr(err);
+                Ln.i(s);
             }
             // Just print the requested data, do not mirror
             return;

Not sure if we can leverage this to improve Ln.i() (and others) without side effects (I'm not sure).

rom1v added a commit that referenced this pull request Oct 31, 2023
Add an option to list the device cameras.

PR #4213 <#4213>

Co-authored-by: Romain Vimont <[email protected]>
Signed-off-by: Romain Vimont <[email protected]>
rom1v added a commit that referenced this pull request Oct 31, 2023
Add an option to list the device camera declared sizes.

PR #4213 <#4213>
rom1v added a commit that referenced this pull request Oct 31, 2023
Add --video-source=camera, and related options:
 - --camera-id=<id>: select the camera by its id (see --list-cameras);
 - --camera-size=<width>x<height>: select the capture size.

Fixed #241 <#241>
PR #4213 <#4213>

Co-authored-by: Romain Vimont <[email protected]>
Signed-off-by: Romain Vimont <[email protected]>
rom1v added a commit that referenced this pull request Oct 31, 2023
If --audio-source is not specified, select the default value
according to the video source:
 - for display mirroring, use device audio by default;
 - for camera mirroring, use microphone by default.

PR #4213 <#4213>
rom1v added a commit that referenced this pull request Oct 31, 2023
Stop mirroring on camera disconnection.

PR #4213 <#4213>
rom1v added a commit that referenced this pull request Oct 31, 2023
If no camera id is provided, use the first camera available.

PR #4213 <#4213>
rom1v added a commit that referenced this pull request Oct 31, 2023
Add an option to select the camera by its lens facing (front, back or
external).

PR #4213 <#4213>

Co-authored-by: Romain Vimont <[email protected]>
Signed-off-by: Romain Vimont <[email protected]>
rom1v added a commit that referenced this pull request Oct 31, 2023
In addition to --camera-size to specify an explicit size, make it
possible to select the camera size automatically, respecting the maximum
size (already used for display mirroring) and an aspect ratio.

For example, "scrcpy --video-source=camera" followed by:
 - (no additional arguments)
    : mirrors at the maximum size, any a-r
 - -m1920
    : only consider valid sizes having both dimensions not above 1920
 - --camera-ar=4:3
    : only consider valid sizes having an aspect ratio of 4:3 (+/- 10%)
 - -m2048 --camera-ar=1.6
    : only consider valid sizes having both dimensions not above 2048
      and an aspect ratio of 1.6 (+/- 10%)

PR #4213 <#4213>

Co-authored-by: Simon Chan <[email protected]>
rom1v added a commit that referenced this pull request Oct 31, 2023
PR #4213 <#4213>

Co-authored-by: Romain Vimont <[email protected]>
Signed-off-by: Romain Vimont <[email protected]>
rom1v added a commit that referenced this pull request Oct 31, 2023
Limit to the variable type size, for consistency.

PR #4213 <#4213>
rom1v pushed a commit that referenced this pull request Oct 31, 2023
Add a new option for specifying the camera frame rate.

By default, Android's default frame rate (30 fps) is used.

PR #4213 <#4213>

Signed-off-by: Andrew Gunnerson <[email protected]>
Signed-off-by: Romain Vimont <[email protected]>
rom1v added a commit that referenced this pull request Oct 31, 2023
Add --camera-high-speed to enable high frame rate camera capture. If
the option is enabled, then --camera-fps is mandatory.

PR #4213 <#4213>

Co-authored-by: Romain Vimont <[email protected]>
Signed-off-by: Andrew Gunnerson <[email protected]>
Signed-off-by: Romain Vimont <[email protected]>
rom1v added a commit that referenced this pull request Oct 31, 2023
rom1v added a commit that referenced this pull request Oct 31, 2023
Xiaomi device ROMs print internal errors using e.printStackTRace(),
flooding the console with irrelevant errors.

To get rid of them, on Xiaomi devices, trash the messages printed to the
console by direct calls to System.out and System.err.

Refs #994 <#994>
Refs #4213 <#4213>
@rom1v
Copy link
Collaborator

rom1v commented Oct 31, 2023

@yume-chan I propose this to get rid of all internal errors printed to the console on Xiaomi devices: 3c7a6d3.
It seems to work well.

Maybe it could be the behavior on all devices instead. What do you think?

@yume-chan
Copy link
Contributor Author

I can confirm my workaround works on my Mi 11 running stock MIUI 14 and Android 13. But again, my workaround only hides one of the two system outputs, so your method of replacing output streams looks like a better solution.

I also checked all your changes on the camera.36 branch, everything looks good. In 120FPS high-speed capture mode, I can get around 50ms of latency, it's much better than 30 FPS normal-speed capture. About arbitrary resolutions and frame rates, my MI 11 only supports the resolutions and frame rates listed in --list-camera-sizes.

@rom1v
Copy link
Collaborator

rom1v commented Nov 1, 2023

Thank you for your feedback. 👍

I simplified the commit to get rid of internal errors, and applied it to all devices: 55a15ad (camera.38.

rom1v added a commit that referenced this pull request Nov 1, 2023
Some devices (mostly Xiaomi) print internal errors using
e.printStackTrace(), flooding the console with irrelevant errors.

Disable system streams used via System.out and System.err streams, to
print only the logs from scrcpy.

Refs #994 <#994>
Refs #4213 <#4213>
rom1v added a commit that referenced this pull request Nov 1, 2023
Some devices (mostly Xiaomi) print internal errors using
e.printStackTrace(), flooding the console with irrelevant errors.

Disable system streams used via System.out and System.err streams, to
print only the logs from scrcpy.

Refs #994 <#994>
Refs #4213 <#4213>
@rom1v
Copy link
Collaborator

rom1v commented Nov 1, 2023

Merged and published scrcpy v2.2 🚀

Thank you very much @yume-chan and @chenxiaolong ❤️

@rom1v rom1v closed this Nov 1, 2023
@yume-chan
Copy link
Contributor Author

I was testing #4392, but since 2 of my 4 devices are running Android 11, I enabled foreground workaround. But then I found this:

diff --git a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java
index b9da3658..ee25663e 100644
--- a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java
+++ b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java
@@ -4,6 +4,8 @@ import com.genymobile.scrcpy.wrappers.ServiceManager;
 
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
+import android.content.ComponentName;
+import android.content.Intent;
 import android.graphics.Rect;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
@@ -64,6 +66,34 @@ public class CameraCapture extends SurfaceCapture {
         this.highSpeed = highSpeed;
     }
 
+    private CameraDevice retryOpenCamera(String id) throws  InterruptedException{
+        Ln.i("Waiting for audio capture to start");
+        Thread.sleep(2000);
+
+        Ln.w("Hacking into your device to forcefully enable camera...");
+        int retry = 0;
+        while (true) {
+            try {
+                Intent intent = new Intent(Intent.ACTION_MAIN);
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                intent.addCategory(Intent.CATEGORY_LAUNCHER);
+                intent.setComponent(new ComponentName(FakeContext.PACKAGE_NAME, "com.android.shell.HeapDumpActivity"));
+                ServiceManager.getActivityManager().startActivityAsUserWithFeature(intent);
+                ServiceManager.getActivityManager().startActivityAsUserWithFeature(intent);
+                ServiceManager.getActivityManager().forceStopPackage(FakeContext.PACKAGE_NAME);
+
+                Thread.sleep(100);
+
+                CameraDevice device = openCamera(id);
+                Ln.i("Your device has been hacked after " + retry + " retries");
+                return device;
+            } catch (Throwable e) {
+                Thread.sleep(1000);
+                retry += 1;
+            }
+        }
+    }
+
     @Override
     public void init() throws IOException {
         cameraThread = new HandlerThread("camera");
@@ -83,7 +113,11 @@ public class CameraCapture extends SurfaceCapture {
             }
 
             Ln.i("Using camera '" + cameraId + "'");
-            cameraDevice = openCamera(cameraId);
+            if(Build.VERSION.SDK_INT==Build.VERSION_CODES.R) {
+                cameraDevice = retryOpenCamera(cameraId);
+            }else{
+                cameraDevice=openCamera(cameraId);
+            }
         } catch (CameraAccessException | InterruptedException e) {
             throw new IOException(e);
         }

By starting foreground workaround twice, then kill it once, then wait for some time, I can open the camera without any restrictions. I don't need to keep foreground workaround running, I can switch to other apps, the camera capture won't stop.

This thing can also work for audio capture, but the success rate is much lower. I can't explain why it works, and I don't think it should be included into Scrcpy. Just a funny story.

@Mufanc
Copy link

Mufanc commented Nov 18, 2023

I tried to create a transparent and not touchable floaty window in scrcpy-server to make camera mirroring work for Android 11 devices. I tested it in AVD and it works fine, but I don't have any more Android 11 devices to test it on, and I can't find any reason in the AOSP why such a design would work. Here are some of the changes I made:
https://github.com/Genymobile/scrcpy/compare/master...Mufanc:scrcpy:feat/camera-a11?expand=1

@yume-chan
Copy link
Contributor Author

@Mufanc It works on Meizu Mblu 10 (Flyme 9, Android 11), but on OnePlus 6 (HydrogenOS, Android 11) it throws an error:

> ./scrcpy -s 9719052f --video-source=camera
scrcpy v2.2 <https://github.com/Genymobile/scrcpy>
INFO: Camera video source: control disabled
INFO: Camera video source: microphone audio source selected
INFO: ADB device found:
INFO:           (usb)              73294bba            device  M2011K2C
INFO:     -->   (usb)              9719052f            device  ONEPLUS_A6000
INFO:           (usb)  M01046406Y2031500412            device  mblu10
C:\Users\simon\Downloads\scrcpy-win64-v2.2\scrcpy-server: 1 file pushed, 0 skipped. 98.7 MB/s (72892 bytes in 0.001s)
[server] INFO: Device: [OnePlus] OnePlus ONEPLUS A6000 (Android 11)
[server] INFO: Using camera '0'
[server] ERROR: Exception on thread Thread[video,5,main]
java.lang.SecurityException: Given calling package android does not match caller's uid 2000
        at android.os.Parcel.createExceptionOrNull(Parcel.java:2373)
        at android.os.Parcel.createException(Parcel.java:2357)
        at android.os.Parcel.readException(Parcel.java:2340)
        at android.os.Parcel.readException(Parcel.java:2282)
        at android.app.IActivityManager$Stub$Proxy.getContentProvider(IActivityManager.java:5855)
        at android.app.ActivityThread.acquireProvider(ActivityThread.java:7169)
        at android.app.ContextImpl$ApplicationContentResolver.acquireProvider(ContextImpl.java:2951)
        at android.content.ContentResolver.acquireProvider(ContentResolver.java:2509)
        at android.provider.Settings$ContentProviderHolder.getProvider(Settings.java:2608)
        at android.provider.Settings$NameValueCache.getStringForUser(Settings.java:2762)
        at android.provider.Settings$Secure.getStringForUser(Settings.java:6052)
        at android.provider.Settings$Secure.getIntForUser(Settings.java:6249)
        at android.provider.Settings$Secure.getInt(Settings.java:6243)
        at android.view.ViewRootImplInjector.handleGestureConfigCheck(ViewRootImplInjector.java:411)
        at android.view.ViewRootImplInjector.<init>(ViewRootImplInjector.java:330)
        at android.view.ViewRootImpl.<init>(ViewRootImpl.java:855)
        at android.view.ViewRootImpl.<init>(ViewRootImpl.java:791)
        at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:411)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:110)
        at com.genymobile.scrcpy.CameraCapture.prepareOverlayWindow(CameraCapture.java:270)
        at com.genymobile.scrcpy.CameraCapture.init(CameraCapture.java:94)
        at com.genymobile.scrcpy.SurfaceEncoder.streamScreen(SurfaceEncoder.java:55)
        at com.genymobile.scrcpy.SurfaceEncoder.lambda$start$0$com-genymobile-scrcpy-SurfaceEncoder(SurfaceEncoder.java:253)
        at com.genymobile.scrcpy.SurfaceEncoder$$ExternalSyntheticLambda0.run(Unknown Source:4)
        at java.lang.Thread.run(Thread.java:923)
Caused by: android.os.RemoteException: Remote stack trace:
        at com.android.server.am.ActivityManagerService.getContentProvider(ActivityManagerService.java:8671)
        at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:2443)
        at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:3321)
        at android.os.Binder.execTransactInternal(Binder.java:1165)
        at android.os.Binder.execTransact(Binder.java:1134)

IIRC there is no way for creating content provider in shell process yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants