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:
1
AUTHORS
1
AUTHORS
@ -110,6 +110,7 @@ Telenor Digital AS <*@telenor.com>
|
|||||||
Temasys Communications <*@temasys.io>
|
Temasys Communications <*@temasys.io>
|
||||||
The Chromium Authors <*@chromium.org>
|
The Chromium Authors <*@chromium.org>
|
||||||
The WebRTC Authors <*@webrtc.org>
|
The WebRTC Authors <*@webrtc.org>
|
||||||
|
Twilio, Inc. <*@twilio.com>
|
||||||
Videxio AS <*@videxio.com>
|
Videxio AS <*@videxio.com>
|
||||||
Vidyo, Inc. <*@vidyo.com>
|
Vidyo, Inc. <*@vidyo.com>
|
||||||
Vonage Holdings Corp. <*@vonage.com>
|
Vonage Holdings Corp. <*@vonage.com>
|
||||||
|
|||||||
@ -61,6 +61,12 @@ public interface CameraVideoCapturer extends VideoCapturer {
|
|||||||
*/
|
*/
|
||||||
void switchCamera(CameraSwitchHandler switchEventsHandler);
|
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
|
* MediaRecorder add/remove handler - one of these functions are invoked with the result of
|
||||||
* addMediaRecorderToCamera() or removeMediaRecorderFromCamera calls.
|
* addMediaRecorderToCamera() or removeMediaRecorderFromCamera calls.
|
||||||
|
|||||||
@ -114,6 +114,12 @@ public class Camera1CapturerUsingByteBufferTest {
|
|||||||
fixtures.switchCamera();
|
fixtures.switchCamera();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
public void testSwitchVideoCapturerToSpecificCameraName() throws InterruptedException {
|
||||||
|
fixtures.switchCamera(true /* specifyCameraName */);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@MediumTest
|
@MediumTest
|
||||||
public void testCameraEvents() throws InterruptedException {
|
public void testCameraEvents() throws InterruptedException {
|
||||||
|
|||||||
@ -109,6 +109,12 @@ public class Camera1CapturerUsingTextureTest {
|
|||||||
fixtures.switchCamera();
|
fixtures.switchCamera();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
public void testSwitchVideoCapturerToSpecificCameraName() throws InterruptedException {
|
||||||
|
fixtures.switchCamera(true /* specifyCameraName */);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@MediumTest
|
@MediumTest
|
||||||
public void testCameraEvents() throws InterruptedException {
|
public void testCameraEvents() throws InterruptedException {
|
||||||
|
|||||||
@ -237,6 +237,12 @@ public class Camera2CapturerTest {
|
|||||||
fixtures.switchCamera();
|
fixtures.switchCamera();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
public void testSwitchVideoCapturerToSpecificCameraName() throws InterruptedException {
|
||||||
|
fixtures.switchCamera(true /* specifyCameraName */);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@MediumTest
|
@MediumTest
|
||||||
public void testCameraEvents() throws InterruptedException {
|
public void testCameraEvents() throws InterruptedException {
|
||||||
|
|||||||
@ -472,6 +472,10 @@ class CameraVideoCapturerTestFixtures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void switchCamera() throws InterruptedException {
|
public void switchCamera() throws InterruptedException {
|
||||||
|
switchCamera(false /* specifyCameraName */);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void switchCamera(boolean specifyCameraName) throws InterruptedException {
|
||||||
if (!testObjectFactory.haveTwoCameras()) {
|
if (!testObjectFactory.haveTwoCameras()) {
|
||||||
Logging.w(
|
Logging.w(
|
||||||
TAG, "Skipping test switch video capturer because the device doesn't have two cameras.");
|
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.
|
// Array with one element to avoid final problem in nested classes.
|
||||||
final boolean[] cameraSwitchSuccessful = new boolean[1];
|
final boolean[] cameraSwitchSuccessful = new boolean[1];
|
||||||
final CountDownLatch barrier = new CountDownLatch(1);
|
final CountDownLatch barrier = new CountDownLatch(1);
|
||||||
capturerInstance.capturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() {
|
final CameraVideoCapturer.CameraSwitchHandler cameraSwitchHandler =
|
||||||
@Override
|
new CameraVideoCapturer.CameraSwitchHandler() {
|
||||||
public void onCameraSwitchDone(boolean isFrontCamera) {
|
@Override
|
||||||
cameraSwitchSuccessful[0] = true;
|
public void onCameraSwitchDone(boolean isFrontCamera) {
|
||||||
barrier.countDown();
|
cameraSwitchSuccessful[0] = true;
|
||||||
}
|
barrier.countDown();
|
||||||
@Override
|
}
|
||||||
public void onCameraSwitchError(String errorDescription) {
|
@Override
|
||||||
cameraSwitchSuccessful[0] = false;
|
public void onCameraSwitchError(String errorDescription) {
|
||||||
barrier.countDown();
|
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.
|
// Wait until the camera has been switched.
|
||||||
barrier.await();
|
barrier.await();
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import android.os.Handler;
|
|||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
abstract class CameraCapturer implements CameraVideoCapturer {
|
abstract class CameraCapturer implements CameraVideoCapturer {
|
||||||
@ -56,8 +57,10 @@ abstract class CameraCapturer implements CameraVideoCapturer {
|
|||||||
switchEventsHandler = null;
|
switchEventsHandler = null;
|
||||||
}
|
}
|
||||||
} else if (switchState == SwitchState.PENDING) {
|
} else if (switchState == SwitchState.PENDING) {
|
||||||
|
String selectedCameraName = pendingCameraName;
|
||||||
|
pendingCameraName = null;
|
||||||
switchState = SwitchState.IDLE;
|
switchState = SwitchState.IDLE;
|
||||||
switchCameraInternal(switchEventsHandler);
|
switchCameraInternal(switchEventsHandler, selectedCameraName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,6 +187,7 @@ abstract class CameraCapturer implements CameraVideoCapturer {
|
|||||||
private boolean sessionOpening; /* guarded by stateLock */
|
private boolean sessionOpening; /* guarded by stateLock */
|
||||||
@Nullable private CameraSession currentSession; /* guarded by stateLock */
|
@Nullable private CameraSession currentSession; /* guarded by stateLock */
|
||||||
private String cameraName; /* guarded by stateLock */
|
private String cameraName; /* guarded by stateLock */
|
||||||
|
private String pendingCameraName; /* guarded by stateLock */
|
||||||
private int width; /* guarded by stateLock */
|
private int width; /* guarded by stateLock */
|
||||||
private int height; /* guarded by stateLock */
|
private int height; /* guarded by stateLock */
|
||||||
private int framerate; /* guarded by stateLock */
|
private int framerate; /* guarded by stateLock */
|
||||||
@ -216,14 +220,13 @@ abstract class CameraCapturer implements CameraVideoCapturer {
|
|||||||
this.eventsHandler = eventsHandler;
|
this.eventsHandler = eventsHandler;
|
||||||
this.cameraEnumerator = cameraEnumerator;
|
this.cameraEnumerator = cameraEnumerator;
|
||||||
this.cameraName = cameraName;
|
this.cameraName = cameraName;
|
||||||
|
List<String> deviceNames = Arrays.asList(cameraEnumerator.getDeviceNames());
|
||||||
uiThreadHandler = new Handler(Looper.getMainLooper());
|
uiThreadHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
final String[] deviceNames = cameraEnumerator.getDeviceNames();
|
if (deviceNames.isEmpty()) {
|
||||||
|
|
||||||
if (deviceNames.length == 0) {
|
|
||||||
throw new RuntimeException("No cameras attached.");
|
throw new RuntimeException("No cameras attached.");
|
||||||
}
|
}
|
||||||
if (!Arrays.asList(deviceNames).contains(this.cameraName)) {
|
if (!deviceNames.contains(this.cameraName)) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Camera name " + this.cameraName + " does not match any known camera device.");
|
"Camera name " + this.cameraName + " does not match any known camera device.");
|
||||||
}
|
}
|
||||||
@ -330,7 +333,27 @@ abstract class CameraCapturer implements CameraVideoCapturer {
|
|||||||
cameraThreadHandler.post(new Runnable() {
|
cameraThreadHandler.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
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");
|
Logging.d(TAG, "switchCamera internal");
|
||||||
|
List<String> deviceNames = Arrays.asList(cameraEnumerator.getDeviceNames());
|
||||||
|
|
||||||
final String[] deviceNames = cameraEnumerator.getDeviceNames();
|
if (!deviceNames.contains(selectedCameraName)) {
|
||||||
|
reportCameraSwitchError("Attempted to switch to unknown camera device " + selectedCameraName,
|
||||||
if (deviceNames.length < 2) {
|
switchEventsHandler);
|
||||||
if (switchEventsHandler != null) {
|
|
||||||
switchEventsHandler.onCameraSwitchError("No camera to switch to.");
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,6 +411,7 @@ abstract class CameraCapturer implements CameraVideoCapturer {
|
|||||||
this.switchEventsHandler = switchEventsHandler;
|
this.switchEventsHandler = switchEventsHandler;
|
||||||
if (sessionOpening) {
|
if (sessionOpening) {
|
||||||
switchState = SwitchState.PENDING;
|
switchState = SwitchState.PENDING;
|
||||||
|
pendingCameraName = selectedCameraName;
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
switchState = SwitchState.IN_PROGRESS;
|
switchState = SwitchState.IN_PROGRESS;
|
||||||
@ -406,8 +429,7 @@ abstract class CameraCapturer implements CameraVideoCapturer {
|
|||||||
});
|
});
|
||||||
currentSession = null;
|
currentSession = null;
|
||||||
|
|
||||||
int cameraNameIndex = Arrays.asList(deviceNames).indexOf(cameraName);
|
cameraName = selectedCameraName;
|
||||||
cameraName = deviceNames[(cameraNameIndex + 1) % deviceNames.length];
|
|
||||||
|
|
||||||
sessionOpening = true;
|
sessionOpening = true;
|
||||||
openAttemptsRemaining = 1;
|
openAttemptsRemaining = 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user