OLD | NEW |
(Empty) | |
| 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 org.webrtc.CameraEnumerationAndroid.CaptureFormat; |
| 14 import org.webrtc.Metrics.Histogram; |
| 15 |
| 16 import android.content.Context; |
| 17 import android.os.Handler; |
| 18 import android.os.SystemClock; |
| 19 import android.view.Surface; |
| 20 import android.view.WindowManager; |
| 21 |
| 22 import java.io.IOException; |
| 23 import java.nio.ByteBuffer; |
| 24 import java.util.List; |
| 25 import java.util.concurrent.CountDownLatch; |
| 26 import java.util.concurrent.TimeUnit; |
| 27 |
| 28 @SuppressWarnings("deprecation") |
| 29 public class Camera1Session implements CameraSession { |
| 30 private static final String TAG = "Camera1Session"; |
| 31 private static final int NUMBER_OF_CAPTURE_BUFFERS = 3; |
| 32 |
| 33 private static final Histogram camera1StartTimeMsHistogram = |
| 34 Histogram.createCounts("WebRTC.Android.Camera1.StartTimeMs", 1, 10000, 50)
; |
| 35 private static final Histogram camera1StopTimeMsHistogram = |
| 36 Histogram.createCounts("WebRTC.Android.Camera1.StopTimeMs", 1, 10000, 50); |
| 37 |
| 38 private static enum SessionState { RUNNING, STOPPED }; |
| 39 |
| 40 private final Handler cameraThreadHandler; |
| 41 private final Events events; |
| 42 private final boolean captureToTexture; |
| 43 private final Context applicationContext; |
| 44 private final SurfaceTextureHelper surfaceTextureHelper; |
| 45 private final int cameraId; |
| 46 private final int width; |
| 47 private final int height; |
| 48 private final int framerate; |
| 49 private final android.hardware.Camera camera; |
| 50 private final android.hardware.Camera.CameraInfo info; |
| 51 private final CaptureFormat captureFormat; |
| 52 // Used only for stats. Only used on the camera thread. |
| 53 private final long constructionTimeNs; // Construction time of this class. |
| 54 |
| 55 private SessionState state; |
| 56 private boolean firstFrameReported = false; |
| 57 |
| 58 public static void create( |
| 59 final CreateSessionCallback callback, final Events events, |
| 60 final boolean captureToTexture, final Context applicationContext, |
| 61 final SurfaceTextureHelper surfaceTextureHelper, |
| 62 final int cameraId, final int width, final int height, final int framerate
) { |
| 63 final long constructionTimeNs = System.nanoTime(); |
| 64 Logging.d(TAG, "Open camera " + cameraId); |
| 65 events.onCameraOpening(); |
| 66 |
| 67 final android.hardware.Camera camera; |
| 68 try { |
| 69 camera = android.hardware.Camera.open(cameraId); |
| 70 } catch (RuntimeException e) { |
| 71 callback.onFailure(e.getMessage()); |
| 72 return; |
| 73 } |
| 74 |
| 75 try { |
| 76 camera.setPreviewTexture(surfaceTextureHelper.getSurfaceTexture()); |
| 77 } catch (IOException e) { |
| 78 camera.release(); |
| 79 callback.onFailure(e.getMessage()); |
| 80 return; |
| 81 } |
| 82 |
| 83 final android.hardware.Camera.CameraInfo info = new android.hardware.Camera.
CameraInfo(); |
| 84 android.hardware.Camera.getCameraInfo(cameraId, info); |
| 85 |
| 86 final android.hardware.Camera.Parameters parameters = camera.getParameters()
; |
| 87 final CaptureFormat captureFormat = findClosestCaptureFormat( |
| 88 parameters, width, height, framerate); |
| 89 final Size pictureSize = findClosestPictureSize(parameters, width, height); |
| 90 |
| 91 updateCameraParameters(camera, parameters, captureFormat, pictureSize, captu
reToTexture); |
| 92 |
| 93 // Initialize the capture buffers. |
| 94 if (!captureToTexture) { |
| 95 final int frameSize = captureFormat.frameSize(); |
| 96 for (int i = 0; i < NUMBER_OF_CAPTURE_BUFFERS; ++i) { |
| 97 final ByteBuffer buffer = ByteBuffer.allocateDirect(frameSize); |
| 98 camera.addCallbackBuffer(buffer.array()); |
| 99 } |
| 100 } |
| 101 |
| 102 // Calculate orientation manually and send it as CVO insted. |
| 103 camera.setDisplayOrientation(0 /* degrees */); |
| 104 |
| 105 callback.onDone(new Camera1Session( |
| 106 events, captureToTexture, applicationContext, surfaceTextureHelper, |
| 107 cameraId, width, height, framerate, |
| 108 camera, info, captureFormat, constructionTimeNs)); |
| 109 } |
| 110 |
| 111 private static void updateCameraParameters(android.hardware.Camera camera, |
| 112 android.hardware.Camera.Parameters parameters, CaptureFormat captureFormat
, Size pictureSize, |
| 113 boolean captureToTexture) { |
| 114 final List<String> focusModes = parameters.getSupportedFocusModes(); |
| 115 |
| 116 parameters.setPreviewFpsRange(captureFormat.framerate.min, captureFormat.fra
merate.max); |
| 117 parameters.setPreviewSize(captureFormat.width, captureFormat.height); |
| 118 parameters.setPictureSize(pictureSize.width, pictureSize.height); |
| 119 if (!captureToTexture) { |
| 120 parameters.setPreviewFormat(captureFormat.imageFormat); |
| 121 } |
| 122 |
| 123 if (parameters.isVideoStabilizationSupported()) { |
| 124 parameters.setVideoStabilization(true); |
| 125 } |
| 126 if (focusModes.contains(android.hardware.Camera.Parameters.FOCUS_MODE_CONTIN
UOUS_VIDEO)) { |
| 127 parameters.setFocusMode(android.hardware.Camera.Parameters.FOCUS_MODE_CONT
INUOUS_VIDEO); |
| 128 } |
| 129 camera.setParameters(parameters); |
| 130 } |
| 131 |
| 132 private static CaptureFormat findClosestCaptureFormat( |
| 133 android.hardware.Camera.Parameters parameters, int width, int height, int
framerate) { |
| 134 // Find closest supported format for |width| x |height| @ |framerate|. |
| 135 final List<CaptureFormat.FramerateRange> supportedFramerates = |
| 136 Camera1Enumerator.convertFramerates(parameters.getSupportedPreviewFpsRan
ge()); |
| 137 Logging.d(TAG, "Available fps ranges: " + supportedFramerates); |
| 138 |
| 139 final CaptureFormat.FramerateRange fpsRange = |
| 140 CameraEnumerationAndroid.getClosestSupportedFramerateRange( |
| 141 supportedFramerates, framerate); |
| 142 |
| 143 final Size previewSize = CameraEnumerationAndroid.getClosestSupportedSize( |
| 144 Camera1Enumerator.convertSizes(parameters.getSupportedPreviewSizes()), |
| 145 width, height); |
| 146 |
| 147 return new CaptureFormat(previewSize.width, previewSize.height, fpsRange); |
| 148 } |
| 149 |
| 150 private static Size findClosestPictureSize(android.hardware.Camera.Parameters
parameters, |
| 151 int width, int height) { |
| 152 return CameraEnumerationAndroid.getClosestSupportedSize( |
| 153 Camera1Enumerator.convertSizes(parameters.getSupportedPictureSizes()), |
| 154 width, height); |
| 155 } |
| 156 |
| 157 private Camera1Session( |
| 158 Events events, boolean captureToTexture, |
| 159 Context applicationContext, SurfaceTextureHelper surfaceTextureHelper, |
| 160 int cameraId, int width, int height, int framerate, |
| 161 android.hardware.Camera camera, android.hardware.Camera.CameraInfo info, |
| 162 CaptureFormat captureFormat, long constructionTimeNs) { |
| 163 Logging.d(TAG, "Create new camera1 session on camera " + cameraId); |
| 164 |
| 165 this.cameraThreadHandler = new Handler(); |
| 166 this.events = events; |
| 167 this.captureToTexture = captureToTexture; |
| 168 this.applicationContext = applicationContext; |
| 169 this.surfaceTextureHelper = surfaceTextureHelper; |
| 170 this.cameraId = cameraId; |
| 171 this.width = width; |
| 172 this.height = height; |
| 173 this.framerate = framerate; |
| 174 this.camera = camera; |
| 175 this.info = info; |
| 176 this.captureFormat = captureFormat; |
| 177 this.constructionTimeNs = constructionTimeNs; |
| 178 |
| 179 startCapturing(); |
| 180 } |
| 181 |
| 182 @Override |
| 183 public void stop() { |
| 184 final long stopStartTime = System.nanoTime(); |
| 185 Logging.d(TAG, "Stop camera1 session on camera " + cameraId); |
| 186 if (Thread.currentThread() == cameraThreadHandler.getLooper().getThread()) { |
| 187 if (state != SessionState.STOPPED) { |
| 188 state = SessionState.STOPPED; |
| 189 // Post the stopInternal to return earlier. |
| 190 cameraThreadHandler.post(new Runnable() { |
| 191 @Override |
| 192 public void run() { |
| 193 stopInternal(); |
| 194 final int stopTimeMs = |
| 195 (int) TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - stopStar
tTime); |
| 196 camera1StopTimeMsHistogram.addSample(stopTimeMs); |
| 197 } |
| 198 }); |
| 199 } |
| 200 } else { |
| 201 final CountDownLatch stopLatch = new CountDownLatch(1); |
| 202 |
| 203 cameraThreadHandler.post(new Runnable() { |
| 204 @Override |
| 205 public void run() { |
| 206 if (state != SessionState.STOPPED) { |
| 207 state = SessionState.STOPPED; |
| 208 stopLatch.countDown(); |
| 209 stopInternal(); |
| 210 final int stopTimeMs = |
| 211 (int) TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - stopStar
tTime); |
| 212 camera1StopTimeMsHistogram.addSample(stopTimeMs); |
| 213 } |
| 214 } |
| 215 }); |
| 216 |
| 217 ThreadUtils.awaitUninterruptibly(stopLatch); |
| 218 } |
| 219 } |
| 220 |
| 221 private void startCapturing() { |
| 222 Logging.d(TAG, "Start capturing"); |
| 223 checkIsOnCameraThread(); |
| 224 |
| 225 state = SessionState.RUNNING; |
| 226 |
| 227 camera.setErrorCallback(new android.hardware.Camera.ErrorCallback() { |
| 228 @Override |
| 229 public void onError(int error, android.hardware.Camera camera) { |
| 230 String errorMessage; |
| 231 if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) { |
| 232 errorMessage = "Camera server died!"; |
| 233 } else { |
| 234 errorMessage = "Camera error: " + error; |
| 235 } |
| 236 Logging.e(TAG, errorMessage); |
| 237 state = SessionState.STOPPED; |
| 238 stopInternal(); |
| 239 events.onCameraError(Camera1Session.this, errorMessage); |
| 240 } |
| 241 }); |
| 242 |
| 243 if (captureToTexture) { |
| 244 listenForTextureFrames(); |
| 245 } else { |
| 246 listenForBytebufferFrames(); |
| 247 } |
| 248 try { |
| 249 camera.startPreview(); |
| 250 } catch (RuntimeException e) { |
| 251 state = SessionState.STOPPED; |
| 252 stopInternal(); |
| 253 events.onCameraError(this, e.getMessage()); |
| 254 } |
| 255 } |
| 256 |
| 257 private void stopInternal() { |
| 258 Logging.d(TAG, "Stop internal"); |
| 259 checkIsOnCameraThread(); |
| 260 |
| 261 surfaceTextureHelper.stopListening(); |
| 262 |
| 263 // Note: stopPreview or other driver code might deadlock. Deadlock in |
| 264 // android.hardware.Camera._stopPreview(Native Method) has been observed on |
| 265 // Nexus 5 (hammerhead), OS version LMY48I. |
| 266 camera.stopPreview(); |
| 267 camera.release(); |
| 268 events.onCameraClosed(this); |
| 269 |
| 270 Logging.d(TAG, "Stop done"); |
| 271 } |
| 272 |
| 273 private void listenForTextureFrames() { |
| 274 surfaceTextureHelper.startListening(new SurfaceTextureHelper.OnTextureFrameA
vailableListener() { |
| 275 @Override |
| 276 public void onTextureFrameAvailable( |
| 277 int oesTextureId, float[] transformMatrix, long timestampNs) { |
| 278 checkIsOnCameraThread(); |
| 279 |
| 280 if (state != SessionState.RUNNING) { |
| 281 Logging.d(TAG, "Texture frame captured but camera is no longer running
."); |
| 282 surfaceTextureHelper.returnTextureFrame(); |
| 283 return; |
| 284 } |
| 285 |
| 286 if (!firstFrameReported) { |
| 287 final int startTimeMs = |
| 288 (int) TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - constructi
onTimeNs); |
| 289 camera1StartTimeMsHistogram.addSample(startTimeMs); |
| 290 firstFrameReported = true; |
| 291 } |
| 292 |
| 293 int rotation = getFrameOrientation(); |
| 294 if (info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRON
T) { |
| 295 // Undo the mirror that the OS "helps" us with. |
| 296 // http://developer.android.com/reference/android/hardware/Camera.html
#setDisplayOrientation(int) |
| 297 transformMatrix = RendererCommon.multiplyMatrices( |
| 298 transformMatrix, RendererCommon.horizontalFlipMatrix()); |
| 299 } |
| 300 events.onTextureFrameCaptured(Camera1Session.this, captureFormat.width, |
| 301 captureFormat.height, oesTextureId, transformMatrix, rotation, times
tampNs); |
| 302 } |
| 303 }); |
| 304 } |
| 305 |
| 306 private void listenForBytebufferFrames() { |
| 307 camera.setPreviewCallbackWithBuffer(new android.hardware.Camera.PreviewCallb
ack() { |
| 308 @Override |
| 309 public void onPreviewFrame(byte[] data, android.hardware.Camera callbackCa
mera) { |
| 310 checkIsOnCameraThread(); |
| 311 |
| 312 if (callbackCamera != camera) { |
| 313 Logging.e(TAG, "Callback from a different camera. This should never ha
ppen."); |
| 314 return; |
| 315 } |
| 316 |
| 317 if (state != SessionState.RUNNING) { |
| 318 Logging.d(TAG, "Bytebuffer frame captured but camera is no longer runn
ing."); |
| 319 return; |
| 320 } |
| 321 |
| 322 final long captureTimeNs = TimeUnit.MILLISECONDS.toNanos(SystemClock.ela
psedRealtime()); |
| 323 |
| 324 if (!firstFrameReported) { |
| 325 final int startTimeMs = |
| 326 (int) TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - constructi
onTimeNs); |
| 327 camera1StartTimeMsHistogram.addSample(startTimeMs); |
| 328 firstFrameReported = true; |
| 329 } |
| 330 |
| 331 events.onByteBufferFrameCaptured(Camera1Session.this, data, captureForma
t.width, |
| 332 captureFormat.height, getFrameOrientation(), captureTimeNs); |
| 333 camera.addCallbackBuffer(data); |
| 334 } |
| 335 }); |
| 336 } |
| 337 |
| 338 private int getDeviceOrientation() { |
| 339 int orientation = 0; |
| 340 |
| 341 WindowManager wm = (WindowManager) applicationContext.getSystemService( |
| 342 Context.WINDOW_SERVICE); |
| 343 switch(wm.getDefaultDisplay().getRotation()) { |
| 344 case Surface.ROTATION_90: |
| 345 orientation = 90; |
| 346 break; |
| 347 case Surface.ROTATION_180: |
| 348 orientation = 180; |
| 349 break; |
| 350 case Surface.ROTATION_270: |
| 351 orientation = 270; |
| 352 break; |
| 353 case Surface.ROTATION_0: |
| 354 default: |
| 355 orientation = 0; |
| 356 break; |
| 357 } |
| 358 return orientation; |
| 359 } |
| 360 |
| 361 private int getFrameOrientation() { |
| 362 int rotation = getDeviceOrientation(); |
| 363 if (info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK) { |
| 364 rotation = 360 - rotation; |
| 365 } |
| 366 return (info.orientation + rotation) % 360; |
| 367 } |
| 368 |
| 369 private void checkIsOnCameraThread() { |
| 370 if (Thread.currentThread() != cameraThreadHandler.getLooper().getThread()) { |
| 371 throw new IllegalStateException("Wrong thread"); |
| 372 } |
| 373 } |
| 374 } |
OLD | NEW |