| Index: webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java
|
| diff --git a/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java b/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java
|
| deleted file mode 100644
|
| index 15c86c9bbe5dd7b6967935a628f6cfe29513c962..0000000000000000000000000000000000000000
|
| --- a/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java
|
| +++ /dev/null
|
| @@ -1,463 +0,0 @@
|
| -/*
|
| - * Copyright (c) 2012 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.videoengine;
|
| -
|
| -import java.io.IOException;
|
| -import java.util.List;
|
| -import java.util.concurrent.Exchanger;
|
| -
|
| -import android.content.Context;
|
| -import android.graphics.ImageFormat;
|
| -import android.graphics.SurfaceTexture;
|
| -import android.hardware.Camera.Parameters;
|
| -import android.hardware.Camera.PreviewCallback;
|
| -import android.hardware.Camera;
|
| -import android.opengl.GLES11Ext;
|
| -import android.opengl.GLES20;
|
| -import android.os.Handler;
|
| -import android.os.Looper;
|
| -import android.os.SystemClock;
|
| -import android.view.Surface;
|
| -import android.view.SurfaceHolder.Callback;
|
| -import android.view.SurfaceHolder;
|
| -import android.view.WindowManager;
|
| -
|
| -import org.webrtc.Logging;
|
| -
|
| -// Wrapper for android Camera, with support for direct local preview rendering.
|
| -// Threading notes: this class is called from ViE C++ code, and from Camera &
|
| -// SurfaceHolder Java callbacks. Since these calls happen on different threads,
|
| -// the entry points to this class are all synchronized. This shouldn't present
|
| -// a performance bottleneck because only onPreviewFrame() is called more than
|
| -// once (and is called serially on a single thread), so the lock should be
|
| -// uncontended. Note that each of these synchronized methods must check
|
| -// |camera| for null to account for having possibly waited for stopCapture() to
|
| -// complete.
|
| -public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
| - private final static String TAG = "WEBRTC-JC";
|
| -
|
| - private static SurfaceHolder localPreview;
|
| - private Camera camera; // Only non-null while capturing.
|
| - private CameraThread cameraThread;
|
| - private Handler cameraThreadHandler;
|
| - private Context context;
|
| - private final int id;
|
| - private final Camera.CameraInfo info;
|
| - private final long native_capturer; // |VideoCaptureAndroid*| in C++.
|
| - private SurfaceTexture cameraSurfaceTexture;
|
| - private int[] cameraGlTextures = null;
|
| - // Arbitrary queue depth. Higher number means more memory allocated & held,
|
| - // lower number means more sensitivity to processing time in the client (and
|
| - // potentially stalling the capturer if it runs out of buffers to write to).
|
| - private final int numCaptureBuffers = 3;
|
| - private double averageDurationMs;
|
| - private long lastCaptureTimeMs;
|
| - private int frameCount;
|
| - private int frameDropRatio;
|
| -
|
| - // Requests future capturers to send their frames to |localPreview| directly.
|
| - public static void setLocalPreview(SurfaceHolder localPreview) {
|
| - // It is a gross hack that this is a class-static. Doing it right would
|
| - // mean plumbing this through the C++ API and using it from
|
| - // webrtc/examples/android/media_demo's MediaEngine class.
|
| - VideoCaptureAndroid.localPreview = localPreview;
|
| - }
|
| -
|
| - public VideoCaptureAndroid(int id, long native_capturer) {
|
| - this.id = id;
|
| - this.native_capturer = native_capturer;
|
| - this.context = GetContext();
|
| - this.info = new Camera.CameraInfo();
|
| - Camera.getCameraInfo(id, info);
|
| - }
|
| -
|
| - // Return the global application context.
|
| - private static native Context GetContext();
|
| -
|
| - private class CameraThread extends Thread {
|
| - private Exchanger<Handler> handlerExchanger;
|
| - public CameraThread(Exchanger<Handler> handlerExchanger) {
|
| - this.handlerExchanger = handlerExchanger;
|
| - }
|
| -
|
| - @Override public void run() {
|
| - Looper.prepare();
|
| - exchange(handlerExchanger, new Handler());
|
| - Looper.loop();
|
| - }
|
| - }
|
| -
|
| - // Called by native code. Returns true if capturer is started.
|
| - //
|
| - // Note that this actually opens the camera, and Camera callbacks run on the
|
| - // thread that calls open(), so this is done on the CameraThread. Since ViE
|
| - // API needs a synchronous success return value we wait for the result.
|
| - private synchronized boolean startCapture(
|
| - final int width, final int height,
|
| - final int min_mfps, final int max_mfps) {
|
| - Logging.d(TAG, "startCapture: " + width + "x" + height + "@" +
|
| - min_mfps + ":" + max_mfps);
|
| - if (cameraThread != null || cameraThreadHandler != null) {
|
| - throw new RuntimeException("Camera thread already started!");
|
| - }
|
| - Exchanger<Handler> handlerExchanger = new Exchanger<Handler>();
|
| - cameraThread = new CameraThread(handlerExchanger);
|
| - cameraThread.start();
|
| - cameraThreadHandler = exchange(handlerExchanger, null);
|
| -
|
| - final Exchanger<Boolean> result = new Exchanger<Boolean>();
|
| - cameraThreadHandler.post(new Runnable() {
|
| - @Override public void run() {
|
| - startCaptureOnCameraThread(width, height, min_mfps, max_mfps, result);
|
| - }
|
| - });
|
| - boolean startResult = exchange(result, false); // |false| is a dummy value.
|
| - return startResult;
|
| - }
|
| -
|
| - private void startCaptureOnCameraThread(
|
| - int width, int height, int min_mfps, int max_mfps,
|
| - Exchanger<Boolean> result) {
|
| - Throwable error = null;
|
| - try {
|
| - camera = Camera.open(id);
|
| -
|
| - if (localPreview != null) {
|
| - localPreview.addCallback(this);
|
| - if (localPreview.getSurface() != null &&
|
| - localPreview.getSurface().isValid()) {
|
| - camera.setPreviewDisplay(localPreview);
|
| - }
|
| - } else {
|
| - // No local renderer (we only care about onPreviewFrame() buffers, not a
|
| - // directly-displayed UI element). Camera won't capture without
|
| - // setPreview{Texture,Display}, so we create a SurfaceTexture and hand
|
| - // it over to Camera, but never listen for frame-ready callbacks,
|
| - // and never call updateTexImage on it.
|
| - try {
|
| - cameraGlTextures = new int[1];
|
| - // Generate one texture pointer and bind it as an external texture.
|
| - GLES20.glGenTextures(1, cameraGlTextures, 0);
|
| - GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
|
| - cameraGlTextures[0]);
|
| - GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
|
| - GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
|
| - GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
|
| - GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
|
| - GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
|
| - GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
|
| - GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
|
| - GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
|
| -
|
| - cameraSurfaceTexture = new SurfaceTexture(cameraGlTextures[0]);
|
| - cameraSurfaceTexture.setOnFrameAvailableListener(null);
|
| - camera.setPreviewTexture(cameraSurfaceTexture);
|
| - } catch (IOException e) {
|
| - throw new RuntimeException(e);
|
| - }
|
| - }
|
| -
|
| - Logging.d(TAG, "Camera orientation: " + info.orientation +
|
| - " .Device orientation: " + getDeviceOrientation());
|
| - Camera.Parameters parameters = camera.getParameters();
|
| - Logging.d(TAG, "isVideoStabilizationSupported: " +
|
| - parameters.isVideoStabilizationSupported());
|
| - if (parameters.isVideoStabilizationSupported()) {
|
| - parameters.setVideoStabilization(true);
|
| - }
|
| - parameters.setPictureSize(width, height);
|
| - parameters.setPreviewSize(width, height);
|
| -
|
| - // Check if requested fps range is supported by camera,
|
| - // otherwise calculate frame drop ratio.
|
| - List<int[]> supportedFpsRanges = parameters.getSupportedPreviewFpsRange();
|
| - frameDropRatio = Integer.MAX_VALUE;
|
| - for (int i = 0; i < supportedFpsRanges.size(); i++) {
|
| - int[] range = supportedFpsRanges.get(i);
|
| - if (range[Parameters.PREVIEW_FPS_MIN_INDEX] == min_mfps &&
|
| - range[Parameters.PREVIEW_FPS_MAX_INDEX] == max_mfps) {
|
| - frameDropRatio = 1;
|
| - break;
|
| - }
|
| - if (range[Parameters.PREVIEW_FPS_MIN_INDEX] % min_mfps == 0 &&
|
| - range[Parameters.PREVIEW_FPS_MAX_INDEX] % max_mfps == 0) {
|
| - int dropRatio = range[Parameters.PREVIEW_FPS_MAX_INDEX] / max_mfps;
|
| - frameDropRatio = Math.min(dropRatio, frameDropRatio);
|
| - }
|
| - }
|
| - if (frameDropRatio == Integer.MAX_VALUE) {
|
| - Logging.e(TAG, "Can not find camera fps range");
|
| - error = new RuntimeException("Can not find camera fps range");
|
| - exchange(result, false);
|
| - return;
|
| - }
|
| - if (frameDropRatio > 1) {
|
| - Logging.d(TAG, "Frame dropper is enabled. Ratio: " + frameDropRatio);
|
| - }
|
| - min_mfps *= frameDropRatio;
|
| - max_mfps *= frameDropRatio;
|
| - Logging.d(TAG, "Camera preview mfps range: " + min_mfps + " - " + max_mfps);
|
| - parameters.setPreviewFpsRange(min_mfps, max_mfps);
|
| -
|
| - int format = ImageFormat.NV21;
|
| - parameters.setPreviewFormat(format);
|
| - camera.setParameters(parameters);
|
| - int bufSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
|
| - for (int i = 0; i < numCaptureBuffers; i++) {
|
| - camera.addCallbackBuffer(new byte[bufSize]);
|
| - }
|
| - camera.setPreviewCallbackWithBuffer(this);
|
| - frameCount = 0;
|
| - averageDurationMs = 1000000.0f / (max_mfps / frameDropRatio);
|
| - camera.startPreview();
|
| - exchange(result, true);
|
| - return;
|
| - } catch (IOException e) {
|
| - error = e;
|
| - } catch (RuntimeException e) {
|
| - error = e;
|
| - }
|
| - Logging.e(TAG, "startCapture failed", error);
|
| - if (camera != null) {
|
| - Exchanger<Boolean> resultDropper = new Exchanger<Boolean>();
|
| - stopCaptureOnCameraThread(resultDropper);
|
| - exchange(resultDropper, false);
|
| - }
|
| - exchange(result, false);
|
| - return;
|
| - }
|
| -
|
| - // Called by native code. Returns true when camera is known to be stopped.
|
| - private synchronized boolean stopCapture() {
|
| - Logging.d(TAG, "stopCapture");
|
| - final Exchanger<Boolean> result = new Exchanger<Boolean>();
|
| - cameraThreadHandler.post(new Runnable() {
|
| - @Override public void run() {
|
| - stopCaptureOnCameraThread(result);
|
| - }
|
| - });
|
| - boolean status = exchange(result, false); // |false| is a dummy value here.
|
| - try {
|
| - cameraThread.join();
|
| - } catch (InterruptedException e) {
|
| - throw new RuntimeException(e);
|
| - }
|
| - cameraThreadHandler = null;
|
| - cameraThread = null;
|
| - Logging.d(TAG, "stopCapture done");
|
| - return status;
|
| - }
|
| -
|
| - private void stopCaptureOnCameraThread(
|
| - Exchanger<Boolean> result) {
|
| - if (camera == null) {
|
| - throw new RuntimeException("Camera is already stopped!");
|
| - }
|
| - Throwable error = null;
|
| - try {
|
| - camera.stopPreview();
|
| - camera.setPreviewCallbackWithBuffer(null);
|
| - if (localPreview != null) {
|
| - localPreview.removeCallback(this);
|
| - camera.setPreviewDisplay(null);
|
| - } else {
|
| - camera.setPreviewTexture(null);
|
| - cameraSurfaceTexture = null;
|
| - if (cameraGlTextures != null) {
|
| - GLES20.glDeleteTextures(1, cameraGlTextures, 0);
|
| - cameraGlTextures = null;
|
| - }
|
| - }
|
| - camera.release();
|
| - camera = null;
|
| - exchange(result, true);
|
| - Looper.myLooper().quit();
|
| - return;
|
| - } catch (IOException e) {
|
| - error = e;
|
| - } catch (RuntimeException e) {
|
| - error = e;
|
| - }
|
| - Logging.e(TAG, "Failed to stop camera", error);
|
| - exchange(result, false);
|
| - Looper.myLooper().quit();
|
| - return;
|
| - }
|
| -
|
| - private int getDeviceOrientation() {
|
| - int orientation = 0;
|
| - if (context != null) {
|
| - WindowManager wm = (WindowManager) context.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 native void ProvideCameraFrame(
|
| - byte[] data, int length, int rotation, long timeStamp, long captureObject);
|
| -
|
| - // Called on cameraThread so must not "synchronized".
|
| - @Override
|
| - public void onPreviewFrame(byte[] data, Camera callbackCamera) {
|
| - if (Thread.currentThread() != cameraThread) {
|
| - throw new RuntimeException("Camera callback not on camera thread?!?");
|
| - }
|
| - if (camera == null) {
|
| - return;
|
| - }
|
| - if (camera != callbackCamera) {
|
| - throw new RuntimeException("Unexpected camera in callback!");
|
| - }
|
| - frameCount++;
|
| - // Check if frame needs to be dropped.
|
| - if ((frameDropRatio > 1) && (frameCount % frameDropRatio) > 0) {
|
| - camera.addCallbackBuffer(data);
|
| - return;
|
| - }
|
| - long captureTimeMs = SystemClock.elapsedRealtime();
|
| - if (frameCount > frameDropRatio) {
|
| - double durationMs = captureTimeMs - lastCaptureTimeMs;
|
| - averageDurationMs = 0.9 * averageDurationMs + 0.1 * durationMs;
|
| - if ((frameCount % 30) == 0) {
|
| - Logging.d(TAG, "Camera TS " + captureTimeMs +
|
| - ". Duration: " + (int)durationMs + " ms. FPS: " +
|
| - (int) (1000 / averageDurationMs + 0.5));
|
| - }
|
| - }
|
| - lastCaptureTimeMs = captureTimeMs;
|
| -
|
| - int rotation = getDeviceOrientation();
|
| - if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
|
| - rotation = 360 - rotation;
|
| - }
|
| - rotation = (info.orientation + rotation) % 360;
|
| -
|
| - ProvideCameraFrame(data, data.length, rotation,
|
| - captureTimeMs, native_capturer);
|
| - camera.addCallbackBuffer(data);
|
| - }
|
| -
|
| - // Sets the rotation of the preview render window.
|
| - // Does not affect the captured video image.
|
| - // Called by native code.
|
| - private synchronized void setPreviewRotation(final int rotation) {
|
| - if (camera == null || cameraThreadHandler == null) {
|
| - return;
|
| - }
|
| - final Exchanger<IOException> result = new Exchanger<IOException>();
|
| - cameraThreadHandler.post(new Runnable() {
|
| - @Override public void run() {
|
| - setPreviewRotationOnCameraThread(rotation, result);
|
| - }
|
| - });
|
| - // Use the exchanger below to block this function until
|
| - // setPreviewRotationOnCameraThread() completes, holding the synchronized
|
| - // lock for the duration. The exchanged value itself is ignored.
|
| - exchange(result, null);
|
| - }
|
| -
|
| - private void setPreviewRotationOnCameraThread(
|
| - int rotation, Exchanger<IOException> result) {
|
| - Logging.v(TAG, "setPreviewRotation:" + rotation);
|
| -
|
| - int resultRotation = 0;
|
| - if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
| - // This is a front facing camera. SetDisplayOrientation will flip
|
| - // the image horizontally before doing the rotation.
|
| - resultRotation = ( 360 - rotation ) % 360; // Compensate for the mirror.
|
| - } else {
|
| - // Back-facing camera.
|
| - resultRotation = rotation;
|
| - }
|
| - camera.setDisplayOrientation(resultRotation);
|
| - exchange(result, null);
|
| - }
|
| -
|
| - @Override
|
| - public synchronized void surfaceChanged(
|
| - SurfaceHolder holder, int format, int width, int height) {
|
| - Logging.d(TAG, "VideoCaptureAndroid::surfaceChanged ignored: " +
|
| - format + ": " + width + "x" + height);
|
| - }
|
| -
|
| - @Override
|
| - public synchronized void surfaceCreated(final SurfaceHolder holder) {
|
| - Logging.d(TAG, "VideoCaptureAndroid::surfaceCreated");
|
| - if (camera == null || cameraThreadHandler == null) {
|
| - return;
|
| - }
|
| - final Exchanger<IOException> result = new Exchanger<IOException>();
|
| - cameraThreadHandler.post(new Runnable() {
|
| - @Override public void run() {
|
| - setPreviewDisplayOnCameraThread(holder, result);
|
| - }
|
| - });
|
| - IOException e = exchange(result, null); // |null| is a dummy value here.
|
| - if (e != null) {
|
| - throw new RuntimeException(e);
|
| - }
|
| - }
|
| -
|
| - @Override
|
| - public synchronized void surfaceDestroyed(SurfaceHolder holder) {
|
| - Logging.d(TAG, "VideoCaptureAndroid::surfaceDestroyed");
|
| - if (camera == null || cameraThreadHandler == null) {
|
| - return;
|
| - }
|
| - final Exchanger<IOException> result = new Exchanger<IOException>();
|
| - cameraThreadHandler.post(new Runnable() {
|
| - @Override public void run() {
|
| - setPreviewDisplayOnCameraThread(null, result);
|
| - }
|
| - });
|
| - IOException e = exchange(result, null); // |null| is a dummy value here.
|
| - if (e != null) {
|
| - throw new RuntimeException(e);
|
| - }
|
| - }
|
| -
|
| - private void setPreviewDisplayOnCameraThread(
|
| - SurfaceHolder holder, Exchanger<IOException> result) {
|
| - try {
|
| - camera.setPreviewDisplay(holder);
|
| - } catch (IOException e) {
|
| - exchange(result, e);
|
| - return;
|
| - }
|
| - exchange(result, null);
|
| - return;
|
| - }
|
| -
|
| - // Exchanges |value| with |exchanger|, converting InterruptedExceptions to
|
| - // RuntimeExceptions (since we expect never to see these).
|
| - private static <T> T exchange(Exchanger<T> exchanger, T value) {
|
| - try {
|
| - return exchanger.exchange(value);
|
| - } catch (InterruptedException e) {
|
| - throw new RuntimeException(e);
|
| - }
|
| - }
|
| -}
|
|
|