Update Android camera switch API to allow specifying a name

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 <sakal@webrtc.org>
Commit-Queue: Sami Kalliomäki <sakal@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#30199}
This commit is contained in:
Aaron Alaniz
2020-01-09 04:26:04 +00:00
committed by Commit Bot
parent e1cbb9c20e
commit 415e39da56
7 changed files with 85 additions and 27 deletions

View File

@ -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>

View File

@ -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.

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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,7 +491,8 @@ 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() {
final CameraVideoCapturer.CameraSwitchHandler cameraSwitchHandler =
new CameraVideoCapturer.CameraSwitchHandler() {
@Override
public void onCameraSwitchDone(boolean isFrontCamera) {
cameraSwitchSuccessful[0] = true;
@ -498,7 +503,13 @@ class CameraVideoCapturerTestFixtures {
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();

View File

@ -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<String> 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<String> 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<String> 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;