Attempt to open Android camera later if it is already in use.

This change VideoCapturerAndroid to attempt 3 times with a period of 300ms to open the camera if it fails.
This is so that if another application have it already opened, it would have more time to release it.
BUG=b/25190234

Review URL: https://codereview.webrtc.org/1422023007

Cr-Commit-Position: refs/heads/master@{#10559}
This commit is contained in:
perkj 2015-11-09 01:35:20 -08:00 committed by Commit bot
parent 1ebf8ba368
commit 89ef6cc13e
3 changed files with 147 additions and 13 deletions

View File

@ -29,7 +29,6 @@ package org.webrtc;
import android.test.ActivityTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import android.util.Size;
import org.webrtc.CameraEnumerationAndroid.CaptureFormat;
@ -222,6 +221,41 @@ public class VideoCapturerAndroidTest extends ActivityTestCase {
getInstrumentation().getContext());
}
@SmallTest
// This test that an error is reported if the camera is already opened
// when VideoCapturerAndroid is started.
public void testStartWhileCameraAlreadyOpened() throws InterruptedException {
String deviceName = CameraEnumerationAndroid.getDeviceName(0);
VideoCapturerAndroid capturer =
VideoCapturerAndroid.create(deviceName, null);
VideoCapturerAndroidTestFixtures.startWhileCameraIsAlreadyOpen(
capturer, getInstrumentation().getContext());
}
@SmallTest
// This test that VideoCapturerAndroid can be started, even if the camera is already opened
// if the camera is closed while VideoCapturerAndroid is re-trying to start.
public void testStartWhileCameraIsAlreadyOpenAndCloseCamera() throws InterruptedException {
String deviceName = CameraEnumerationAndroid.getDeviceName(0);
VideoCapturerAndroid capturer =
VideoCapturerAndroid.create(deviceName, null);
VideoCapturerAndroidTestFixtures.startWhileCameraIsAlreadyOpenAndCloseCamera(
capturer, getInstrumentation().getContext());
}
@SmallTest
// This test that VideoCapturerAndroid.stop can be called while VideoCapturerAndroid is
// re-trying to start.
public void startWhileCameraIsAlreadyOpenAndStop() throws InterruptedException {
String deviceName = CameraEnumerationAndroid.getDeviceName(0);
VideoCapturerAndroid capturer =
VideoCapturerAndroid.create(deviceName, null);
VideoCapturerAndroidTestFixtures.startWhileCameraIsAlreadyOpenAndStop(
capturer, getInstrumentation().getContext());
}
@SmallTest
// This test what happens if buffers are returned after the capturer have
// been stopped and restarted. It does not test or use the C++ layer.
@ -259,7 +293,6 @@ public class VideoCapturerAndroidTest extends ActivityTestCase {
VideoCapturerAndroidTestFixtures.returnBufferLateEndToEnd(capturer);
}
@MediumTest
// This test that CameraEventsHandler.onError is triggered if video buffers are not returned to
// the capturer.

View File

@ -29,6 +29,7 @@ package org.webrtc;
import android.content.Context;
import android.hardware.Camera;
import org.webrtc.VideoCapturerAndroidTestFixtures;
import org.webrtc.CameraEnumerationAndroid.CaptureFormat;
import org.webrtc.VideoRenderer.I420Frame;
@ -382,6 +383,71 @@ public class VideoCapturerAndroidTestFixtures {
assertTrue(capturer.isReleased());
}
static void waitUntilIdle(VideoCapturerAndroid capturer) throws InterruptedException {
final CountDownLatch barrier = new CountDownLatch(1);
capturer.getCameraThreadHandler().post(new Runnable() {
@Override public void run() {
barrier.countDown();
}
});
barrier.await();
}
static public void startWhileCameraIsAlreadyOpen(
VideoCapturerAndroid capturer, Context appContext) throws InterruptedException {
Camera camera = Camera.open(capturer.getCurrentCameraId());
final List<CaptureFormat> formats = capturer.getSupportedFormats();
final CameraEnumerationAndroid.CaptureFormat format = formats.get(0);
final FakeCapturerObserver observer = new FakeCapturerObserver();
capturer.startCapture(format.width, format.height, format.maxFramerate,
appContext, observer);
assertFalse(observer.WaitForCapturerToStart());
capturer.dispose();
camera.release();
}
static public void startWhileCameraIsAlreadyOpenAndCloseCamera(
VideoCapturerAndroid capturer, Context appContext) throws InterruptedException {
Camera camera = Camera.open(capturer.getCurrentCameraId());
final List<CaptureFormat> formats = capturer.getSupportedFormats();
final CameraEnumerationAndroid.CaptureFormat format = formats.get(0);
final FakeCapturerObserver observer = new FakeCapturerObserver();
capturer.startCapture(format.width, format.height, format.maxFramerate,
appContext, observer);
waitUntilIdle(capturer);
camera.release();
// Make sure camera is started and first frame is received and then stop it.
assertTrue(observer.WaitForCapturerToStart());
observer.WaitForNextCapturedFrame();
capturer.stopCapture();
for (long timeStamp : observer.getCopyAndResetListOftimeStamps()) {
capturer.returnBuffer(timeStamp);
}
capturer.dispose();
assertTrue(capturer.isReleased());
}
static public void startWhileCameraIsAlreadyOpenAndStop(
VideoCapturerAndroid capturer, Context appContext) throws InterruptedException {
Camera camera = Camera.open(capturer.getCurrentCameraId());
final List<CaptureFormat> formats = capturer.getSupportedFormats();
final CameraEnumerationAndroid.CaptureFormat format = formats.get(0);
final FakeCapturerObserver observer = new FakeCapturerObserver();
capturer.startCapture(format.width, format.height, format.maxFramerate,
appContext, observer);
capturer.stopCapture();
capturer.dispose();
assertTrue(capturer.isReleased());
camera.release();
}
static public void returnBufferLate(VideoCapturerAndroid capturer,
Context appContext) throws InterruptedException {
FakeCapturerObserver observer = new FakeCapturerObserver();

View File

@ -100,6 +100,12 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
// The camera API can output one old frame after the camera has been switched or the resolution
// has been changed. This flag is used for dropping the first frame after camera restart.
private boolean dropNextFrame = false;
// |openCameraOnCodecThreadRunner| is used for retrying to open the camera if it is in use by
// another application when startCaptureOnCameraThread is called.
private Runnable openCameraOnCodecThreadRunner;
private final static int MAX_OPEN_CAMERA_ATTEMPTS = 3;
private final static int OPEN_CAMERA_DELAY_MS = 300;
private int openCameraAttempts;
// Camera error callback.
private final Camera.ErrorCallback cameraErrorCallback =
@ -313,7 +319,7 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
// Helper function to retrieve the current camera id synchronously. Note that the camera id might
// change at any point by switchCamera() calls.
private int getCurrentCameraId() {
int getCurrentCameraId() {
synchronized (cameraIdLock) {
return id;
}
@ -423,6 +429,7 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
if (frameObserver == null) {
throw new RuntimeException("frameObserver not set.");
}
cameraThreadHandler.post(new Runnable() {
@Override public void run() {
startCaptureOnCameraThread(width, height, framerate, frameObserver,
@ -432,8 +439,8 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
}
private void startCaptureOnCameraThread(
int width, int height, int framerate, CapturerObserver frameObserver,
Context applicationContext) {
final int width, final int height, final int framerate, final CapturerObserver frameObserver,
final Context applicationContext) {
Throwable error = null;
checkIsOnCameraThread();
if (camera != null) {
@ -441,17 +448,36 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
}
this.applicationContext = applicationContext;
this.frameObserver = frameObserver;
this.firstFrameReported = false;
try {
synchronized (cameraIdLock) {
Logging.d(TAG, "Opening camera " + id);
firstFrameReported = false;
if (eventsHandler != null) {
eventsHandler.onCameraOpening(id);
try {
synchronized (cameraIdLock) {
Logging.d(TAG, "Opening camera " + id);
if (eventsHandler != null) {
eventsHandler.onCameraOpening(id);
}
camera = Camera.open(id);
info = new Camera.CameraInfo();
Camera.getCameraInfo(id, info);
}
camera = Camera.open(id);
info = new Camera.CameraInfo();
Camera.getCameraInfo(id, info);
} catch (RuntimeException e) {
openCameraAttempts++;
if (openCameraAttempts < MAX_OPEN_CAMERA_ATTEMPTS) {
Logging.e(TAG, "Camera.open failed, retrying", e);
openCameraOnCodecThreadRunner = new Runnable() {
@Override public void run() {
startCaptureOnCameraThread(width, height, framerate, frameObserver,
applicationContext);
}
};
cameraThreadHandler.postDelayed(openCameraOnCodecThreadRunner, OPEN_CAMERA_DELAY_MS);
return;
}
openCameraAttempts = 0;
throw new RuntimeException(e);
}
try {
camera.setPreviewTexture(surfaceHelper.getSurfaceTexture());
} catch (IOException e) {
@ -571,6 +597,10 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
private void stopCaptureOnCameraThread() {
checkIsOnCameraThread();
Logging.d(TAG, "stopCaptureOnCameraThread");
if (openCameraOnCodecThreadRunner != null) {
cameraThreadHandler.removeCallbacks(openCameraOnCodecThreadRunner);
}
openCameraAttempts = 0;
if (camera == null) {
Logging.e(TAG, "Calling stopCapture() for already stopped camera.");
return;
@ -622,6 +652,11 @@ public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallba
frameObserver.onOutputFormatRequest(width, height, framerate);
}
// Exposed for testing purposes only.
Handler getCameraThreadHandler() {
return cameraThreadHandler;
}
public void returnBuffer(final long timeStamp) {
cameraThreadHandler.post(new Runnable() {
@Override public void run() {