diff --git a/webrtc/api/java/android/org/webrtc/CameraVideoCapturer.java b/webrtc/api/java/android/org/webrtc/CameraVideoCapturer.java new file mode 100644 index 0000000000..46432d4619 --- /dev/null +++ b/webrtc/api/java/android/org/webrtc/CameraVideoCapturer.java @@ -0,0 +1,128 @@ +/* + * 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; + +/** + * Base interface for camera1 and camera2 implementations. Extends VideoCapturer with a + * switchCamera() function. Also provides subinterfaces for handling camera events, and a helper + * class for detecting camera freezes. + */ +public interface CameraVideoCapturer extends VideoCapturer { + /** + * Camera events handler - can be used to be notifed about camera events. The callbacks are + * executed from an arbitrary thread. + */ + public interface CameraEventsHandler { + // Camera error handler - invoked when camera can not be opened + // or any camera exception happens on camera thread. + void onCameraError(String errorDescription); + + // Invoked when camera stops receiving frames. + void onCameraFreezed(String errorDescription); + + // Callback invoked when camera is opening. + void onCameraOpening(int cameraId); + + // Callback invoked when first camera frame is available after camera is started. + void onFirstFrameAvailable(); + + // Callback invoked when camera is closed. + void onCameraClosed(); + } + + /** + * Camera switch handler - one of these functions are invoked with the result of switchCamera(). + * The callback may be called on an arbitrary thread. + */ + public interface CameraSwitchHandler { + // Invoked on success. |isFrontCamera| is true if the new camera is front facing. + void onCameraSwitchDone(boolean isFrontCamera); + + // Invoked on failure, e.g. camera is stopped or only one camera available. + void onCameraSwitchError(String errorDescription); + } + + /** + * Switch camera to the next valid camera id. This can only be called while the camera is running. + * This function can be called from any thread. + */ + void switchCamera(CameraSwitchHandler switchEventsHandler); + + /** + * Helper class to log framerate and detect if the camera freezes. It will run periodic callbacks + * on the SurfaceTextureHelper thread passed in the ctor, and should only be operated from that + * thread. + */ + public static class CameraStatistics { + private final static String TAG = "CameraStatistics"; + private final static int CAMERA_OBSERVER_PERIOD_MS = 2000; + private final static int CAMERA_FREEZE_REPORT_TIMOUT_MS = 4000; + + private final SurfaceTextureHelper surfaceTextureHelper; + private final CameraEventsHandler eventsHandler; + private int frameCount; + private int freezePeriodCount; + // Camera observer - monitors camera framerate. Observer is executed on camera thread. + private final Runnable cameraObserver = new Runnable() { + @Override + public void run() { + final int cameraFps = Math.round(frameCount * 1000.0f / CAMERA_OBSERVER_PERIOD_MS); + Logging.d(TAG, "Camera fps: " + cameraFps +"."); + if (frameCount == 0) { + ++freezePeriodCount; + if (CAMERA_OBSERVER_PERIOD_MS * freezePeriodCount >= CAMERA_FREEZE_REPORT_TIMOUT_MS + && eventsHandler != null) { + Logging.e(TAG, "Camera freezed."); + if (surfaceTextureHelper.isTextureInUse()) { + // This can only happen if we are capturing to textures. + eventsHandler.onCameraFreezed("Camera failure. Client must return video buffers."); + } else { + eventsHandler.onCameraFreezed("Camera failure."); + } + return; + } + } else { + freezePeriodCount = 0; + } + frameCount = 0; + surfaceTextureHelper.getHandler().postDelayed(this, CAMERA_OBSERVER_PERIOD_MS); + } + }; + + public CameraStatistics( + SurfaceTextureHelper surfaceTextureHelper, CameraEventsHandler eventsHandler) { + if (surfaceTextureHelper == null) { + throw new IllegalArgumentException("SurfaceTextureHelper is null"); + } + this.surfaceTextureHelper = surfaceTextureHelper; + this.eventsHandler = eventsHandler; + this.frameCount = 0; + this.freezePeriodCount = 0; + surfaceTextureHelper.getHandler().postDelayed(cameraObserver, CAMERA_OBSERVER_PERIOD_MS); + } + + private void checkThread() { + if (Thread.currentThread() != surfaceTextureHelper.getHandler().getLooper().getThread()) { + throw new IllegalStateException("Wrong thread"); + } + } + + public void addFrame() { + checkThread(); + ++frameCount; + } + + public void release() { + checkThread(); + surfaceTextureHelper.getHandler().removeCallbacks(cameraObserver); + } + } +} diff --git a/webrtc/api/java/android/org/webrtc/VideoCapturerAndroid.java b/webrtc/api/java/android/org/webrtc/VideoCapturerAndroid.java index 78d042df50..6352bf799f 100644 --- a/webrtc/api/java/android/org/webrtc/VideoCapturerAndroid.java +++ b/webrtc/api/java/android/org/webrtc/VideoCapturerAndroid.java @@ -38,14 +38,13 @@ import java.util.concurrent.TimeUnit; // arbitrary Java threads. All public entry points are thread safe, and delegate the work to the // camera thread. The internal *OnCameraThread() methods must check |camera| for null to check if // the camera has been stopped. +// TODO(magjed): This class name is now confusing - rename to Camera1VideoCapturer. @SuppressWarnings("deprecation") public class VideoCapturerAndroid implements - VideoCapturer, + CameraVideoCapturer, android.hardware.Camera.PreviewCallback, SurfaceTextureHelper.OnTextureFrameAvailableListener { private final static String TAG = "VideoCapturerAndroid"; - private final static int CAMERA_OBSERVER_PERIOD_MS = 2000; - private final static int CAMERA_FREEZE_REPORT_TIMOUT_MS = 4000; private static final int CAMERA_STOP_TIMEOUT_MS = 7000; private boolean isDisposed = false; @@ -60,7 +59,7 @@ public class VideoCapturerAndroid implements private final Object cameraIdLock = new Object(); private int id; private android.hardware.Camera.CameraInfo info; - private final CameraStatistics cameraStatistics; + private CameraStatistics cameraStatistics; // Remember the requested format in case we want to switch cameras. private int requestedWidth; private int requestedHeight; @@ -104,84 +103,6 @@ public class VideoCapturerAndroid implements } }; - // Camera observer - monitors camera framerate. Observer is executed on camera thread. - private final Runnable cameraObserver = new Runnable() { - private int freezePeriodCount; - @Override - public void run() { - int cameraFramesCount = cameraStatistics.getAndResetFrameCount(); - int cameraFps = (cameraFramesCount * 1000 + CAMERA_OBSERVER_PERIOD_MS / 2) - / CAMERA_OBSERVER_PERIOD_MS; - - Logging.d(TAG, "Camera fps: " + cameraFps +"."); - if (cameraFramesCount == 0) { - ++freezePeriodCount; - if (CAMERA_OBSERVER_PERIOD_MS * freezePeriodCount >= CAMERA_FREEZE_REPORT_TIMOUT_MS - && eventsHandler != null) { - Logging.e(TAG, "Camera freezed."); - if (surfaceHelper.isTextureInUse()) { - // This can only happen if we are capturing to textures. - eventsHandler.onCameraFreezed("Camera failure. Client must return video buffers."); - } else { - eventsHandler.onCameraFreezed("Camera failure."); - } - return; - } - } else { - freezePeriodCount = 0; - } - maybePostDelayedOnCameraThread(CAMERA_OBSERVER_PERIOD_MS, this); - } - }; - - private static class CameraStatistics { - private int frameCount = 0; - private final ThreadUtils.ThreadChecker threadChecker = new ThreadUtils.ThreadChecker(); - - CameraStatistics() { - threadChecker.detachThread(); - } - - public void addFrame() { - threadChecker.checkIsOnValidThread(); - ++frameCount; - } - - public int getAndResetFrameCount() { - threadChecker.checkIsOnValidThread(); - int count = frameCount; - frameCount = 0; - return count; - } - } - - public static interface CameraEventsHandler { - // Camera error handler - invoked when camera can not be opened - // or any camera exception happens on camera thread. - void onCameraError(String errorDescription); - - // Invoked when camera stops receiving frames - void onCameraFreezed(String errorDescription); - - // Callback invoked when camera is opening. - void onCameraOpening(int cameraId); - - // Callback invoked when first camera frame is available after camera is opened. - void onFirstFrameAvailable(); - - // Callback invoked when camera closed. - void onCameraClosed(); - } - - // Camera switch handler - one of these functions are invoked with the result of switchCamera(). - // The callback may be called on an arbitrary thread. - public interface CameraSwitchHandler { - // Invoked on success. |isFrontCamera| is true if the new camera is front facing. - void onCameraSwitchDone(boolean isFrontCamera); - // Invoked on failure, e.g. camera is stopped or only one camera available. - void onCameraSwitchError(String errorDescription); - } - public static VideoCapturerAndroid create(String name, CameraEventsHandler eventsHandler) { return VideoCapturerAndroid.create(name, eventsHandler, false /* captureToTexture */); @@ -216,6 +137,7 @@ public class VideoCapturerAndroid implements // Switch camera to the next valid camera id. This can only be called while // the camera is running. + @Override public void switchCamera(final CameraSwitchHandler switchEventsHandler) { if (android.hardware.Camera.getNumberOfCameras() < 2) { if (switchEventsHandler != null) { @@ -299,7 +221,6 @@ public class VideoCapturerAndroid implements this.id = cameraId; this.eventsHandler = eventsHandler; isCapturingToTexture = captureToTexture; - cameraStatistics = new CameraStatistics(); Logging.d(TAG, "VideoCapturerAndroid isCapturingToTexture : " + isCapturingToTexture); } @@ -460,7 +381,7 @@ public class VideoCapturerAndroid implements } // Start camera observer. - maybePostDelayedOnCameraThread(CAMERA_OBSERVER_PERIOD_MS, cameraObserver); + cameraStatistics = new CameraStatistics(surfaceHelper, eventsHandler); return; } catch (RuntimeException e) { error = e; @@ -613,8 +534,10 @@ public class VideoCapturerAndroid implements if (surfaceHelper != null) { surfaceHelper.stopListening(); } - cameraThreadHandler.removeCallbacks(cameraObserver); - cameraStatistics.getAndResetFrameCount(); + if (cameraStatistics != null) { + cameraStatistics.release(); + cameraStatistics = null; + } Logging.d(TAG, "Stop preview."); if (camera != null) { camera.stopPreview();