1 /* 2 * Copyright 2016 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 package org.webrtc; 12 13 import static org.junit.Assert.fail; 14 15 import android.content.Context; 16 import android.hardware.camera2.CameraAccessException; 17 import android.hardware.camera2.CameraDevice; 18 import android.hardware.camera2.CameraManager; 19 import android.os.Handler; 20 import android.os.Looper; 21 import android.support.test.InstrumentationRegistry; 22 import androidx.annotation.Nullable; 23 import androidx.test.filters.LargeTest; 24 import androidx.test.filters.MediumTest; 25 import androidx.test.filters.SmallTest; 26 import java.util.concurrent.CountDownLatch; 27 import org.junit.After; 28 import org.junit.Before; 29 import org.junit.Test; 30 31 public class Camera2CapturerTest { 32 static final String TAG = "Camera2CapturerTest"; 33 34 /** 35 * Simple camera2 implementation that only knows how to open the camera and close it. 36 */ 37 private class SimpleCamera2 { 38 final CameraManager cameraManager; 39 final LooperThread looperThread; 40 final CountDownLatch openDoneSignal; 41 final Object cameraDeviceLock; 42 @Nullable CameraDevice cameraDevice; // Guarded by cameraDeviceLock 43 boolean openSucceeded; // Guarded by cameraDeviceLock 44 45 private class LooperThread extends Thread { 46 final CountDownLatch startedSignal = new CountDownLatch(1); 47 private Handler handler; 48 49 @Override run()50 public void run() { 51 Looper.prepare(); 52 handler = new Handler(); 53 startedSignal.countDown(); 54 Looper.loop(); 55 } 56 waitToStart()57 public void waitToStart() { 58 ThreadUtils.awaitUninterruptibly(startedSignal); 59 } 60 requestStop()61 public void requestStop() { 62 handler.getLooper().quit(); 63 } 64 getHandler()65 public Handler getHandler() { 66 return handler; 67 } 68 } 69 70 private class CameraStateCallback extends CameraDevice.StateCallback { 71 @Override onClosed(CameraDevice cameraDevice)72 public void onClosed(CameraDevice cameraDevice) { 73 Logging.d(TAG, "Simple camera2 closed."); 74 75 synchronized (cameraDeviceLock) { 76 SimpleCamera2.this.cameraDevice = null; 77 } 78 } 79 80 @Override onDisconnected(CameraDevice cameraDevice)81 public void onDisconnected(CameraDevice cameraDevice) { 82 Logging.d(TAG, "Simple camera2 disconnected."); 83 84 synchronized (cameraDeviceLock) { 85 SimpleCamera2.this.cameraDevice = null; 86 } 87 } 88 89 @Override onError(CameraDevice cameraDevice, int errorCode)90 public void onError(CameraDevice cameraDevice, int errorCode) { 91 Logging.w(TAG, "Simple camera2 error: " + errorCode); 92 93 synchronized (cameraDeviceLock) { 94 SimpleCamera2.this.cameraDevice = cameraDevice; 95 openSucceeded = false; 96 } 97 98 openDoneSignal.countDown(); 99 } 100 101 @Override onOpened(CameraDevice cameraDevice)102 public void onOpened(CameraDevice cameraDevice) { 103 Logging.d(TAG, "Simple camera2 opened."); 104 105 synchronized (cameraDeviceLock) { 106 SimpleCamera2.this.cameraDevice = cameraDevice; 107 openSucceeded = true; 108 } 109 110 openDoneSignal.countDown(); 111 } 112 } 113 SimpleCamera2(Context context, String deviceName)114 SimpleCamera2(Context context, String deviceName) { 115 cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); 116 looperThread = new LooperThread(); 117 looperThread.start(); 118 looperThread.waitToStart(); 119 cameraDeviceLock = new Object(); 120 openDoneSignal = new CountDownLatch(1); 121 cameraDevice = null; 122 Logging.d(TAG, "Opening simple camera2."); 123 try { 124 cameraManager.openCamera(deviceName, new CameraStateCallback(), looperThread.getHandler()); 125 } catch (CameraAccessException e) { 126 fail("Simple camera2 CameraAccessException: " + e.getMessage()); 127 } 128 129 Logging.d(TAG, "Waiting for simple camera2 to open."); 130 ThreadUtils.awaitUninterruptibly(openDoneSignal); 131 synchronized (cameraDeviceLock) { 132 if (!openSucceeded) { 133 fail("Opening simple camera2 failed."); 134 } 135 } 136 } 137 close()138 public void close() { 139 Logging.d(TAG, "Closing simple camera2."); 140 synchronized (cameraDeviceLock) { 141 if (cameraDevice != null) { 142 cameraDevice.close(); 143 } 144 } 145 146 looperThread.requestStop(); 147 ThreadUtils.joinUninterruptibly(looperThread); 148 } 149 } 150 151 private class TestObjectFactory extends CameraVideoCapturerTestFixtures.TestObjectFactory { 152 @Override getCameraEnumerator()153 public CameraEnumerator getCameraEnumerator() { 154 return new Camera2Enumerator(getAppContext()); 155 } 156 157 @Override getAppContext()158 public Context getAppContext() { 159 return InstrumentationRegistry.getTargetContext(); 160 } 161 162 @SuppressWarnings("deprecation") 163 @Override rawOpenCamera(String cameraName)164 public Object rawOpenCamera(String cameraName) { 165 return new SimpleCamera2(getAppContext(), cameraName); 166 } 167 168 @SuppressWarnings("deprecation") 169 @Override rawCloseCamera(Object camera)170 public void rawCloseCamera(Object camera) { 171 ((SimpleCamera2) camera).close(); 172 } 173 } 174 175 private CameraVideoCapturerTestFixtures fixtures; 176 177 @Before setUp()178 public void setUp() { 179 fixtures = new CameraVideoCapturerTestFixtures(new TestObjectFactory()); 180 } 181 182 @After tearDown()183 public void tearDown() { 184 fixtures.dispose(); 185 } 186 187 @Test 188 @SmallTest testCreateAndDispose()189 public void testCreateAndDispose() throws InterruptedException { 190 fixtures.createCapturerAndDispose(); 191 } 192 193 @Test 194 @SmallTest testCreateNonExistingCamera()195 public void testCreateNonExistingCamera() throws InterruptedException { 196 fixtures.createNonExistingCamera(); 197 } 198 199 // This test that the camera can be started and that the frames are forwarded 200 // to a Java video renderer using a "default" capturer. 201 // It tests both the Java and the C++ layer. 202 @Test 203 @MediumTest testCreateCapturerAndRender()204 public void testCreateCapturerAndRender() throws InterruptedException { 205 fixtures.createCapturerAndRender(); 206 } 207 208 // This test that the camera can be started and that the frames are forwarded 209 // to a Java video renderer using the front facing video capturer. 210 // It tests both the Java and the C++ layer. 211 @Test 212 @MediumTest testStartFrontFacingVideoCapturer()213 public void testStartFrontFacingVideoCapturer() throws InterruptedException { 214 fixtures.createFrontFacingCapturerAndRender(); 215 } 216 217 // This test that the camera can be started and that the frames are forwarded 218 // to a Java video renderer using the back facing video capturer. 219 // It tests both the Java and the C++ layer. 220 @Test 221 @MediumTest testStartBackFacingVideoCapturer()222 public void testStartBackFacingVideoCapturer() throws InterruptedException { 223 fixtures.createBackFacingCapturerAndRender(); 224 } 225 226 // This test that the default camera can be started and that the camera can 227 // later be switched to another camera. 228 // It tests both the Java and the C++ layer. 229 @Test 230 @MediumTest testSwitchVideoCapturer()231 public void testSwitchVideoCapturer() throws InterruptedException { 232 fixtures.switchCamera(); 233 } 234 235 @Test 236 @MediumTest testSwitchVideoCapturerToSpecificCameraName()237 public void testSwitchVideoCapturerToSpecificCameraName() throws InterruptedException { 238 fixtures.switchCamera(true /* specifyCameraName */); 239 } 240 241 @Test 242 @MediumTest testCameraEvents()243 public void testCameraEvents() throws InterruptedException { 244 fixtures.cameraEventsInvoked(); 245 } 246 247 // Test what happens when attempting to call e.g. switchCamera() after camera has been stopped. 248 @Test 249 @MediumTest testCameraCallsAfterStop()250 public void testCameraCallsAfterStop() throws InterruptedException { 251 fixtures.cameraCallsAfterStop(); 252 } 253 254 // This test that the VideoSource that the CameraVideoCapturer is connected to can 255 // be stopped and restarted. It tests both the Java and the C++ layer. 256 @Test 257 @LargeTest testStopRestartVideoSource()258 public void testStopRestartVideoSource() throws InterruptedException { 259 fixtures.stopRestartVideoSource(); 260 } 261 262 // This test that the camera can be started at different resolutions. 263 // It does not test or use the C++ layer. 264 @Test 265 @LargeTest testStartStopWithDifferentResolutions()266 public void testStartStopWithDifferentResolutions() throws InterruptedException { 267 fixtures.startStopWithDifferentResolutions(); 268 } 269 270 // This test what happens if buffers are returned after the capturer have 271 // been stopped and restarted. It does not test or use the C++ layer. 272 @Test 273 @LargeTest testReturnBufferLate()274 public void testReturnBufferLate() throws InterruptedException { 275 fixtures.returnBufferLate(); 276 } 277 278 // This test that we can capture frames, keep the frames in a local renderer, stop capturing, 279 // and then return the frames. The difference between the test testReturnBufferLate() is that we 280 // also test the JNI and C++ AndroidVideoCapturer parts. 281 @Test 282 @MediumTest testReturnBufferLateEndToEnd()283 public void testReturnBufferLateEndToEnd() throws InterruptedException { 284 fixtures.returnBufferLateEndToEnd(); 285 } 286 287 // This test that CameraEventsHandler.onError is triggered if video buffers are not returned to 288 // the capturer. 289 @Test 290 @LargeTest testCameraFreezedEventOnBufferStarvation()291 public void testCameraFreezedEventOnBufferStarvation() throws InterruptedException { 292 fixtures.cameraFreezedEventOnBufferStarvation(); 293 } 294 295 // This test that frames forwarded to a renderer is scaled if adaptOutputFormat is 296 // called. This test both Java and C++ parts of of the stack. 297 @Test 298 @MediumTest testScaleCameraOutput()299 public void testScaleCameraOutput() throws InterruptedException { 300 fixtures.scaleCameraOutput(); 301 } 302 303 // This test that frames forwarded to a renderer is cropped to a new orientation if 304 // adaptOutputFormat is called in such a way. This test both Java and C++ parts of of the stack. 305 @Test 306 @MediumTest testCropCameraOutput()307 public void testCropCameraOutput() throws InterruptedException { 308 fixtures.cropCameraOutput(); 309 } 310 311 // This test that an error is reported if the camera is already opened 312 // when CameraVideoCapturer is started. 313 @Test 314 @LargeTest testStartWhileCameraIsAlreadyOpen()315 public void testStartWhileCameraIsAlreadyOpen() throws InterruptedException { 316 fixtures.startWhileCameraIsAlreadyOpen(); 317 } 318 319 // This test that CameraVideoCapturer can be started, even if the camera is already opened 320 // if the camera is closed while CameraVideoCapturer is re-trying to start. 321 @Test 322 @LargeTest testStartWhileCameraIsAlreadyOpenAndCloseCamera()323 public void testStartWhileCameraIsAlreadyOpenAndCloseCamera() throws InterruptedException { 324 fixtures.startWhileCameraIsAlreadyOpenAndCloseCamera(); 325 } 326 327 // This test that CameraVideoCapturer.stop can be called while CameraVideoCapturer is 328 // re-trying to start. 329 @Test 330 @MediumTest testStartWhileCameraIsAlreadyOpenAndStop()331 public void testStartWhileCameraIsAlreadyOpenAndStop() throws InterruptedException { 332 fixtures.startWhileCameraIsAlreadyOpenAndStop(); 333 } 334 } 335