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:
parent
1ebf8ba368
commit
89ef6cc13e
@ -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.
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user