| Index: webrtc/api/android/java/src/org/webrtc/Camera2Session.java
|
| diff --git a/webrtc/api/android/java/src/org/webrtc/Camera2Session.java b/webrtc/api/android/java/src/org/webrtc/Camera2Session.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..5e32c27102f95dc01dcf1b678a5e346aa61c5c82
|
| --- /dev/null
|
| +++ b/webrtc/api/android/java/src/org/webrtc/Camera2Session.java
|
| @@ -0,0 +1,409 @@
|
| +/*
|
| + * Copyright 2016 The WebRTC project authors. All Rights Reserved.
|
| + *
|
| + * Use of this source code is governed by a BSD-style license
|
| + * that can be found in the LICENSE file in the root of the source
|
| + * tree. An additional intellectual property rights grant can be found
|
| + * in the file PATENTS. All contributing project authors may
|
| + * be found in the AUTHORS file in the root of the source tree.
|
| + */
|
| +
|
| +package org.webrtc;
|
| +
|
| +import org.webrtc.CameraEnumerationAndroid.CaptureFormat;
|
| +
|
| +import android.annotation.TargetApi;
|
| +import android.content.Context;
|
| +import android.graphics.SurfaceTexture;
|
| +import android.hardware.camera2.CameraAccessException;
|
| +import android.hardware.camera2.CameraCaptureSession;
|
| +import android.hardware.camera2.CameraCharacteristics;
|
| +import android.hardware.camera2.CameraDevice;
|
| +import android.hardware.camera2.CameraManager;
|
| +import android.hardware.camera2.CameraMetadata;
|
| +import android.hardware.camera2.CaptureFailure;
|
| +import android.hardware.camera2.CaptureRequest;
|
| +import android.os.Handler;
|
| +import android.util.Range;
|
| +import android.view.Surface;
|
| +import android.view.WindowManager;
|
| +
|
| +import java.util.Arrays;
|
| +import java.util.List;
|
| +import java.util.concurrent.CountDownLatch;
|
| +
|
| +@TargetApi(21)
|
| +public class Camera2Session implements CameraSession {
|
| + private static final String TAG = "Camera2Session";
|
| +
|
| + private static enum SessionState { RUNNING, STOPPED };
|
| +
|
| + private final Handler cameraThreadHandler;
|
| + private final CameraManager cameraManager;
|
| + private final CreateSessionCallback callback;
|
| + private final CameraVideoCapturer.CameraEventsHandler eventsHandler;
|
| + private final Context applicationContext;
|
| + private final CameraVideoCapturer.CapturerObserver capturerObserver;
|
| + private final SurfaceTextureHelper surfaceTextureHelper;
|
| + private final String cameraId;
|
| + private final int width;
|
| + private final int height;
|
| + private final int framerate;
|
| +
|
| + // Initialized at start
|
| + private CameraCharacteristics cameraCharacteristics;
|
| + private int cameraOrientation;
|
| + private boolean isCameraFrontFacing;
|
| + private int fpsUnitFactor;
|
| + private CaptureFormat captureFormat;
|
| +
|
| + // Initialized when camera opens
|
| + private CameraDevice cameraDevice;
|
| + private Surface surface;
|
| +
|
| + // Initialized when capture session is created
|
| + private CameraCaptureSession captureSession;
|
| + private CameraVideoCapturer.CameraStatistics cameraStatistics;
|
| +
|
| + // State
|
| + private SessionState state = SessionState.RUNNING;
|
| + private boolean firstFrameReported = false;
|
| +
|
| + private class CameraStateCallback extends CameraDevice.StateCallback {
|
| + private String getErrorDescription(int errorCode) {
|
| + switch (errorCode) {
|
| + case CameraDevice.StateCallback.ERROR_CAMERA_DEVICE:
|
| + return "Camera device has encountered a fatal error.";
|
| + case CameraDevice.StateCallback.ERROR_CAMERA_DISABLED:
|
| + return "Camera device could not be opened due to a device policy.";
|
| + case CameraDevice.StateCallback.ERROR_CAMERA_IN_USE:
|
| + return "Camera device is in use already.";
|
| + case CameraDevice.StateCallback.ERROR_CAMERA_SERVICE:
|
| + return "Camera service has encountered a fatal error.";
|
| + case CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE:
|
| + return "Camera device could not be opened because"
|
| + + " there are too many other open camera devices.";
|
| + default:
|
| + return "Unknown camera error: " + errorCode;
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void onDisconnected(CameraDevice camera) {
|
| + checkIsOnCameraThread();
|
| + reportError("Camera disconnected.");
|
| + }
|
| +
|
| + @Override
|
| + public void onError(CameraDevice camera, int errorCode) {
|
| + checkIsOnCameraThread();
|
| + reportError(getErrorDescription(errorCode));
|
| + }
|
| +
|
| + @Override
|
| + public void onOpened(CameraDevice camera) {
|
| + checkIsOnCameraThread();
|
| +
|
| + Logging.d(TAG, "Camera opened.");
|
| + cameraDevice = camera;
|
| +
|
| + final SurfaceTexture surfaceTexture = surfaceTextureHelper.getSurfaceTexture();
|
| + surfaceTexture.setDefaultBufferSize(captureFormat.width, captureFormat.height);
|
| + surface = new Surface(surfaceTexture);
|
| + try {
|
| + camera.createCaptureSession(
|
| + Arrays.asList(surface), new CaptureSessionCallback(), cameraThreadHandler);
|
| + } catch (CameraAccessException e) {
|
| + reportError("Failed to create capture session. " + e);
|
| + return;
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void onClosed(CameraDevice camera) {
|
| + checkIsOnCameraThread();
|
| +
|
| + Logging.d(TAG, "Camera device closed.");
|
| + eventsHandler.onCameraClosed();
|
| + }
|
| + }
|
| +
|
| + private class CaptureSessionCallback extends CameraCaptureSession.StateCallback {
|
| + @Override
|
| + public void onConfigureFailed(CameraCaptureSession session) {
|
| + checkIsOnCameraThread();
|
| + session.close();
|
| + reportError("Failed to configure capture session.");
|
| + }
|
| +
|
| + @Override
|
| + public void onConfigured(CameraCaptureSession session) {
|
| + checkIsOnCameraThread();
|
| + Logging.d(TAG, "Camera capture session configured.");
|
| + captureSession = session;
|
| + try {
|
| + /*
|
| + * The viable options for video capture requests are:
|
| + * TEMPLATE_PREVIEW: High frame rate is given priority over the highest-quality
|
| + * post-processing.
|
| + * TEMPLATE_RECORD: Stable frame rate is used, and post-processing is set for recording
|
| + * quality.
|
| + */
|
| + final CaptureRequest.Builder captureRequestBuilder =
|
| + cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
|
| + // Set auto exposure fps range.
|
| + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, new Range<Integer>(
|
| + captureFormat.framerate.min / fpsUnitFactor,
|
| + captureFormat.framerate.max / fpsUnitFactor));
|
| + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
|
| + CaptureRequest.CONTROL_AE_MODE_ON);
|
| + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false);
|
| +
|
| + captureRequestBuilder.addTarget(surface);
|
| + session.setRepeatingRequest(
|
| + captureRequestBuilder.build(), new CameraCaptureCallback(), cameraThreadHandler);
|
| + } catch (CameraAccessException e) {
|
| + reportError("Failed to start capture request. " + e);
|
| + return;
|
| + }
|
| +
|
| + surfaceTextureHelper.startListening(
|
| + new SurfaceTextureHelper.OnTextureFrameAvailableListener() {
|
| + @Override
|
| + public void onTextureFrameAvailable(
|
| + int oesTextureId, float[] transformMatrix, long timestampNs) {
|
| + checkIsOnCameraThread();
|
| +
|
| + if (state != SessionState.RUNNING) {
|
| + Logging.d(TAG, "Texture frame captured but camera is no longer running.");
|
| + return;
|
| + }
|
| +
|
| + if (!firstFrameReported) {
|
| + eventsHandler.onFirstFrameAvailable();
|
| + firstFrameReported = true;
|
| + }
|
| +
|
| + int rotation = getFrameOrientation();
|
| + if (isCameraFrontFacing) {
|
| + // Undo the mirror that the OS "helps" us with.
|
| + // http://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int)
|
| + transformMatrix = RendererCommon.multiplyMatrices(
|
| + transformMatrix, RendererCommon.horizontalFlipMatrix());
|
| + }
|
| +
|
| + // Undo camera orientation - we report it as rotation instead.
|
| + transformMatrix = RendererCommon.rotateTextureMatrix(
|
| + transformMatrix, -cameraOrientation);
|
| +
|
| + cameraStatistics.addFrame();
|
| + capturerObserver.onTextureFrameCaptured(captureFormat.width, captureFormat.height,
|
| + oesTextureId, transformMatrix, rotation, timestampNs);
|
| + }
|
| + });
|
| + capturerObserver.onCapturerStarted(true /* success */);
|
| + cameraStatistics = new CameraVideoCapturer.CameraStatistics(
|
| + surfaceTextureHelper, eventsHandler);
|
| + Logging.d(TAG, "Camera device successfully started.");
|
| + callback.onDone(Camera2Session.this);
|
| + }
|
| + }
|
| +
|
| + private class CameraCaptureCallback extends CameraCaptureSession.CaptureCallback {
|
| + @Override
|
| + public void onCaptureFailed(
|
| + CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) {
|
| + Logging.d(TAG, "Capture failed: " + failure);
|
| + }
|
| + }
|
| +
|
| + public static void create(
|
| + CameraManager cameraManager, CreateSessionCallback callback,
|
| + CameraVideoCapturer.CameraEventsHandler eventsHandler, Context applicationContext,
|
| + CameraVideoCapturer.CapturerObserver capturerObserver,
|
| + SurfaceTextureHelper surfaceTextureHelper,
|
| + String cameraId, int width, int height, int framerate) {
|
| + new Camera2Session(
|
| + cameraManager, callback,
|
| + eventsHandler, applicationContext,
|
| + capturerObserver,
|
| + surfaceTextureHelper,
|
| + cameraId, width, height, framerate);
|
| + }
|
| +
|
| + private Camera2Session(
|
| + CameraManager cameraManager, CreateSessionCallback callback,
|
| + CameraVideoCapturer.CameraEventsHandler eventsHandler, Context applicationContext,
|
| + CameraVideoCapturer.CapturerObserver capturerObserver,
|
| + SurfaceTextureHelper surfaceTextureHelper,
|
| + String cameraId, int width, int height, int framerate) {
|
| + Logging.d(TAG, "Create new camera2 session on camera " + cameraId);
|
| +
|
| + this.cameraThreadHandler = new Handler();
|
| + this.cameraManager = cameraManager;
|
| + this.callback = callback;
|
| + this.eventsHandler = eventsHandler;
|
| + this.applicationContext = applicationContext;
|
| + this.capturerObserver = capturerObserver;
|
| + this.surfaceTextureHelper = surfaceTextureHelper;
|
| + this.cameraId = cameraId;
|
| + this.width = width;
|
| + this.height = height;
|
| + this.framerate = framerate;
|
| +
|
| + start();
|
| + }
|
| +
|
| + private void start() {
|
| + checkIsOnCameraThread();
|
| + Logging.d(TAG, "start");
|
| +
|
| + try {
|
| + cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
|
| + } catch (final CameraAccessException e) {
|
| + reportError("getCameraCharacteristics(): " + e.getMessage());
|
| + }
|
| + cameraOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
|
| + isCameraFrontFacing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING)
|
| + == CameraMetadata.LENS_FACING_FRONT;
|
| +
|
| + findCaptureFormat();
|
| + openCamera();
|
| + }
|
| +
|
| + private void findCaptureFormat() {
|
| + checkIsOnCameraThread();
|
| +
|
| + Range<Integer>[] fpsRanges =
|
| + cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
|
| + fpsUnitFactor = Camera2Enumerator.getFpsUnitFactor(fpsRanges);
|
| + List<CaptureFormat.FramerateRange> framerateRanges =
|
| + Camera2Enumerator.convertFramerates(fpsRanges, fpsUnitFactor);
|
| + List<Size> sizes = Camera2Enumerator.getSupportedSizes(cameraCharacteristics);
|
| +
|
| + if (framerateRanges.isEmpty() || sizes.isEmpty()) {
|
| + reportError("No supported capture formats.");
|
| + }
|
| +
|
| + final CaptureFormat.FramerateRange bestFpsRange =
|
| + CameraEnumerationAndroid.getClosestSupportedFramerateRange(
|
| + framerateRanges, framerate);
|
| +
|
| + final Size bestSize = CameraEnumerationAndroid.getClosestSupportedSize(
|
| + sizes, width, height);
|
| +
|
| + captureFormat = new CaptureFormat(bestSize.width, bestSize.height, bestFpsRange);
|
| + Logging.d(TAG, "Using capture format: " + captureFormat);
|
| + }
|
| +
|
| + private void openCamera() {
|
| + checkIsOnCameraThread();
|
| +
|
| + Logging.d(TAG, "Opening camera " + cameraId);
|
| + int cameraIndex = -1;
|
| + try {
|
| + cameraIndex = Integer.parseInt(cameraId);
|
| + } catch (NumberFormatException e) {
|
| + Logging.d(TAG, "External camera with non-int identifier: " + cameraId);
|
| + }
|
| + eventsHandler.onCameraOpening(cameraIndex);
|
| +
|
| + try {
|
| + cameraManager.openCamera(cameraId, new CameraStateCallback(), cameraThreadHandler);
|
| + } catch (CameraAccessException e) {
|
| + reportError("Failed to open camera: " + e);
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void stop() {
|
| + Logging.d(TAG, "Stop camera2 session on camera " + cameraId);
|
| + final CountDownLatch stopLatch = new CountDownLatch(1);
|
| +
|
| + cameraThreadHandler.post(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + if (state != SessionState.STOPPED) {
|
| + state = SessionState.STOPPED;
|
| + capturerObserver.onCapturerStopped();
|
| + stopLatch.countDown();
|
| + stopInternal();
|
| + }
|
| + }
|
| + });
|
| +
|
| + ThreadUtils.awaitUninterruptibly(stopLatch);
|
| + }
|
| +
|
| + private void stopInternal() {
|
| + Logging.d(TAG, "Stop internal");
|
| + checkIsOnCameraThread();
|
| +
|
| + surfaceTextureHelper.stopListening();
|
| + cameraStatistics.release();
|
| +
|
| + captureSession.close();
|
| + captureSession = null;
|
| + surface.release();
|
| + surface = null;
|
| + cameraDevice.close();
|
| + cameraDevice = null;
|
| +
|
| + Logging.d(TAG, "Stop done");
|
| + }
|
| +
|
| + private void reportError(String error) {
|
| + checkIsOnCameraThread();
|
| + Logging.e(TAG, "Error: " + error);
|
| +
|
| + if (captureSession == null) {
|
| + if (cameraDevice != null) {
|
| + cameraDevice.close();
|
| + cameraDevice = null;
|
| + }
|
| +
|
| + state = SessionState.STOPPED;
|
| + callback.onFailure(error);
|
| + capturerObserver.onCapturerStarted(false /* success */);
|
| + } else {
|
| + eventsHandler.onCameraError(error);
|
| + }
|
| + }
|
| +
|
| + private int getDeviceOrientation() {
|
| + int orientation = 0;
|
| +
|
| + WindowManager wm = (WindowManager) applicationContext.getSystemService(
|
| + Context.WINDOW_SERVICE);
|
| + switch(wm.getDefaultDisplay().getRotation()) {
|
| + case Surface.ROTATION_90:
|
| + orientation = 90;
|
| + break;
|
| + case Surface.ROTATION_180:
|
| + orientation = 180;
|
| + break;
|
| + case Surface.ROTATION_270:
|
| + orientation = 270;
|
| + break;
|
| + case Surface.ROTATION_0:
|
| + default:
|
| + orientation = 0;
|
| + break;
|
| + }
|
| + return orientation;
|
| + }
|
| +
|
| + private int getFrameOrientation() {
|
| + int rotation = getDeviceOrientation();
|
| + if (!isCameraFrontFacing) {
|
| + rotation = 360 - rotation;
|
| + }
|
| + return (cameraOrientation + rotation) % 360;
|
| + }
|
| +
|
| + private void checkIsOnCameraThread() {
|
| + if (Thread.currentThread() != cameraThreadHandler.getLooper().getThread()) {
|
| + throw new IllegalStateException("Wrong thread");
|
| + }
|
| + }
|
| +}
|
|
|