From 415e39da565ab7ed5e89f2e7a9629801dea56248 Mon Sep 17 00:00:00 2001 From: Aaron Alaniz Date: Thu, 9 Jan 2020 04:26:04 +0000 Subject: [PATCH] Update Android camera switch API to allow specifying a name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current camera switch API sequentially cycles through each camera name for each method invocation. This policy provides reasonable behavior for devices with 2 or 3 cameras, but presents challenges with devices that contain several cameras. For example in a scenario where the current camera is oriented on the same side as the next camera name, a developer would need to call switchCamera multiple times to capture from a camera oriented on a different side of the device. This commit allows a developer to specify a camera name when switching cameras. This flexibility allows developers to have more control over which device they switch to in cases where a device contains several cameras. Bug: webrtc:11261 Change-Id: I93d46d70b2c7cf735a411a4ef4f33e926bf3a5ea Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/165040 Reviewed-by: Sami Kalliomäki Commit-Queue: Sami Kalliomäki Cr-Commit-Position: refs/heads/master@{#30199} --- AUTHORS | 1 + .../api/org/webrtc/CameraVideoCapturer.java | 6 +++ .../Camera1CapturerUsingByteBufferTest.java | 6 +++ .../Camera1CapturerUsingTextureTest.java | 6 +++ .../src/org/webrtc/Camera2CapturerTest.java | 6 +++ .../CameraVideoCapturerTestFixtures.java | 35 ++++++++----- .../src/java/org/webrtc/CameraCapturer.java | 52 +++++++++++++------ 7 files changed, 85 insertions(+), 27 deletions(-) diff --git a/AUTHORS b/AUTHORS index 8b63772068..c9893aef5f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -110,6 +110,7 @@ Telenor Digital AS <*@telenor.com> Temasys Communications <*@temasys.io> The Chromium Authors <*@chromium.org> The WebRTC Authors <*@webrtc.org> +Twilio, Inc. <*@twilio.com> Videxio AS <*@videxio.com> Vidyo, Inc. <*@vidyo.com> Vonage Holdings Corp. <*@vonage.com> diff --git a/sdk/android/api/org/webrtc/CameraVideoCapturer.java b/sdk/android/api/org/webrtc/CameraVideoCapturer.java index de21c1d7e7..88228ab57f 100644 --- a/sdk/android/api/org/webrtc/CameraVideoCapturer.java +++ b/sdk/android/api/org/webrtc/CameraVideoCapturer.java @@ -61,6 +61,12 @@ public interface CameraVideoCapturer extends VideoCapturer { */ void switchCamera(CameraSwitchHandler switchEventsHandler); + /** + * Switch camera to the specified camera id. This can only be called while the camera is running. + * This function can be called from any thread. + */ + void switchCamera(CameraSwitchHandler switchEventsHandler, String cameraName); + /** * MediaRecorder add/remove handler - one of these functions are invoked with the result of * addMediaRecorderToCamera() or removeMediaRecorderFromCamera calls. diff --git a/sdk/android/instrumentationtests/src/org/webrtc/Camera1CapturerUsingByteBufferTest.java b/sdk/android/instrumentationtests/src/org/webrtc/Camera1CapturerUsingByteBufferTest.java index e25c355781..4c8361e334 100644 --- a/sdk/android/instrumentationtests/src/org/webrtc/Camera1CapturerUsingByteBufferTest.java +++ b/sdk/android/instrumentationtests/src/org/webrtc/Camera1CapturerUsingByteBufferTest.java @@ -114,6 +114,12 @@ public class Camera1CapturerUsingByteBufferTest { fixtures.switchCamera(); } + @Test + @MediumTest + public void testSwitchVideoCapturerToSpecificCameraName() throws InterruptedException { + fixtures.switchCamera(true /* specifyCameraName */); + } + @Test @MediumTest public void testCameraEvents() throws InterruptedException { diff --git a/sdk/android/instrumentationtests/src/org/webrtc/Camera1CapturerUsingTextureTest.java b/sdk/android/instrumentationtests/src/org/webrtc/Camera1CapturerUsingTextureTest.java index 2c1b19552e..4f71915c38 100644 --- a/sdk/android/instrumentationtests/src/org/webrtc/Camera1CapturerUsingTextureTest.java +++ b/sdk/android/instrumentationtests/src/org/webrtc/Camera1CapturerUsingTextureTest.java @@ -109,6 +109,12 @@ public class Camera1CapturerUsingTextureTest { fixtures.switchCamera(); } + @Test + @MediumTest + public void testSwitchVideoCapturerToSpecificCameraName() throws InterruptedException { + fixtures.switchCamera(true /* specifyCameraName */); + } + @Test @MediumTest public void testCameraEvents() throws InterruptedException { diff --git a/sdk/android/instrumentationtests/src/org/webrtc/Camera2CapturerTest.java b/sdk/android/instrumentationtests/src/org/webrtc/Camera2CapturerTest.java index 10c6d24579..e351bdf5df 100644 --- a/sdk/android/instrumentationtests/src/org/webrtc/Camera2CapturerTest.java +++ b/sdk/android/instrumentationtests/src/org/webrtc/Camera2CapturerTest.java @@ -237,6 +237,12 @@ public class Camera2CapturerTest { fixtures.switchCamera(); } + @Test + @MediumTest + public void testSwitchVideoCapturerToSpecificCameraName() throws InterruptedException { + fixtures.switchCamera(true /* specifyCameraName */); + } + @Test @MediumTest public void testCameraEvents() throws InterruptedException { diff --git a/sdk/android/instrumentationtests/src/org/webrtc/CameraVideoCapturerTestFixtures.java b/sdk/android/instrumentationtests/src/org/webrtc/CameraVideoCapturerTestFixtures.java index e86ebf3a30..56b744074e 100644 --- a/sdk/android/instrumentationtests/src/org/webrtc/CameraVideoCapturerTestFixtures.java +++ b/sdk/android/instrumentationtests/src/org/webrtc/CameraVideoCapturerTestFixtures.java @@ -472,6 +472,10 @@ class CameraVideoCapturerTestFixtures { } public void switchCamera() throws InterruptedException { + switchCamera(false /* specifyCameraName */); + } + + public void switchCamera(boolean specifyCameraName) throws InterruptedException { if (!testObjectFactory.haveTwoCameras()) { Logging.w( TAG, "Skipping test switch video capturer because the device doesn't have two cameras."); @@ -487,18 +491,25 @@ class CameraVideoCapturerTestFixtures { // Array with one element to avoid final problem in nested classes. final boolean[] cameraSwitchSuccessful = new boolean[1]; final CountDownLatch barrier = new CountDownLatch(1); - capturerInstance.capturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() { - @Override - public void onCameraSwitchDone(boolean isFrontCamera) { - cameraSwitchSuccessful[0] = true; - barrier.countDown(); - } - @Override - public void onCameraSwitchError(String errorDescription) { - cameraSwitchSuccessful[0] = false; - barrier.countDown(); - } - }); + final CameraVideoCapturer.CameraSwitchHandler cameraSwitchHandler = + new CameraVideoCapturer.CameraSwitchHandler() { + @Override + public void onCameraSwitchDone(boolean isFrontCamera) { + cameraSwitchSuccessful[0] = true; + barrier.countDown(); + } + @Override + public void onCameraSwitchError(String errorDescription) { + cameraSwitchSuccessful[0] = false; + barrier.countDown(); + } + }; + if (specifyCameraName) { + String expectedCameraName = testObjectFactory.cameraEnumerator.getDeviceNames()[1]; + capturerInstance.capturer.switchCamera(cameraSwitchHandler, expectedCameraName); + } else { + capturerInstance.capturer.switchCamera(cameraSwitchHandler); + } // Wait until the camera has been switched. barrier.await(); diff --git a/sdk/android/src/java/org/webrtc/CameraCapturer.java b/sdk/android/src/java/org/webrtc/CameraCapturer.java index 15fa9baefd..47519d765f 100644 --- a/sdk/android/src/java/org/webrtc/CameraCapturer.java +++ b/sdk/android/src/java/org/webrtc/CameraCapturer.java @@ -15,6 +15,7 @@ import android.os.Handler; import android.os.Looper; import android.support.annotation.Nullable; import java.util.Arrays; +import java.util.List; @SuppressWarnings("deprecation") abstract class CameraCapturer implements CameraVideoCapturer { @@ -56,8 +57,10 @@ abstract class CameraCapturer implements CameraVideoCapturer { switchEventsHandler = null; } } else if (switchState == SwitchState.PENDING) { + String selectedCameraName = pendingCameraName; + pendingCameraName = null; switchState = SwitchState.IDLE; - switchCameraInternal(switchEventsHandler); + switchCameraInternal(switchEventsHandler, selectedCameraName); } } } @@ -184,6 +187,7 @@ abstract class CameraCapturer implements CameraVideoCapturer { private boolean sessionOpening; /* guarded by stateLock */ @Nullable private CameraSession currentSession; /* guarded by stateLock */ private String cameraName; /* guarded by stateLock */ + private String pendingCameraName; /* guarded by stateLock */ private int width; /* guarded by stateLock */ private int height; /* guarded by stateLock */ private int framerate; /* guarded by stateLock */ @@ -216,14 +220,13 @@ abstract class CameraCapturer implements CameraVideoCapturer { this.eventsHandler = eventsHandler; this.cameraEnumerator = cameraEnumerator; this.cameraName = cameraName; + List deviceNames = Arrays.asList(cameraEnumerator.getDeviceNames()); uiThreadHandler = new Handler(Looper.getMainLooper()); - final String[] deviceNames = cameraEnumerator.getDeviceNames(); - - if (deviceNames.length == 0) { + if (deviceNames.isEmpty()) { throw new RuntimeException("No cameras attached."); } - if (!Arrays.asList(deviceNames).contains(this.cameraName)) { + if (!deviceNames.contains(this.cameraName)) { throw new IllegalArgumentException( "Camera name " + this.cameraName + " does not match any known camera device."); } @@ -330,7 +333,27 @@ abstract class CameraCapturer implements CameraVideoCapturer { cameraThreadHandler.post(new Runnable() { @Override public void run() { - switchCameraInternal(switchEventsHandler); + List deviceNames = Arrays.asList(cameraEnumerator.getDeviceNames()); + + if (deviceNames.size() < 2) { + reportCameraSwitchError("No camera to switch to.", switchEventsHandler); + return; + } + + int cameraNameIndex = deviceNames.indexOf(cameraName); + String cameraName = deviceNames.get((cameraNameIndex + 1) % deviceNames.size()); + switchCameraInternal(switchEventsHandler, cameraName); + } + }); + } + + @Override + public void switchCamera(final CameraSwitchHandler switchEventsHandler, final String cameraName) { + Logging.d(TAG, "switchCamera"); + cameraThreadHandler.post(new Runnable() { + @Override + public void run() { + switchCameraInternal(switchEventsHandler, cameraName); } }); } @@ -364,15 +387,14 @@ abstract class CameraCapturer implements CameraVideoCapturer { } } - private void switchCameraInternal(@Nullable final CameraSwitchHandler switchEventsHandler) { + private void switchCameraInternal( + @Nullable final CameraSwitchHandler switchEventsHandler, final String selectedCameraName) { Logging.d(TAG, "switchCamera internal"); + List deviceNames = Arrays.asList(cameraEnumerator.getDeviceNames()); - final String[] deviceNames = cameraEnumerator.getDeviceNames(); - - if (deviceNames.length < 2) { - if (switchEventsHandler != null) { - switchEventsHandler.onCameraSwitchError("No camera to switch to."); - } + if (!deviceNames.contains(selectedCameraName)) { + reportCameraSwitchError("Attempted to switch to unknown camera device " + selectedCameraName, + switchEventsHandler); return; } @@ -389,6 +411,7 @@ abstract class CameraCapturer implements CameraVideoCapturer { this.switchEventsHandler = switchEventsHandler; if (sessionOpening) { switchState = SwitchState.PENDING; + pendingCameraName = selectedCameraName; return; } else { switchState = SwitchState.IN_PROGRESS; @@ -406,8 +429,7 @@ abstract class CameraCapturer implements CameraVideoCapturer { }); currentSession = null; - int cameraNameIndex = Arrays.asList(deviceNames).indexOf(cameraName); - cameraName = deviceNames[(cameraNameIndex + 1) % deviceNames.length]; + cameraName = selectedCameraName; sessionOpening = true; openAttemptsRemaining = 1;