| Index: media/base/android/java/src/org/chromium/media/VideoCapture.java
|
| ===================================================================
|
| --- media/base/android/java/src/org/chromium/media/VideoCapture.java (revision 0)
|
| +++ media/base/android/java/src/org/chromium/media/VideoCapture.java (revision 0)
|
| @@ -0,0 +1,337 @@
|
| +// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.media;
|
| +
|
| +import android.content.Context;
|
| +import android.graphics.ImageFormat;
|
| +import android.graphics.SurfaceTexture;
|
| +import android.graphics.SurfaceTexture.OnFrameAvailableListener;
|
| +import android.hardware.Camera;
|
| +import android.hardware.Camera.PreviewCallback;
|
| +import android.opengl.GLES11Ext;
|
| +import android.opengl.GLES20;
|
| +import android.util.Log;
|
| +import android.view.Surface;
|
| +import android.view.WindowManager;
|
| +
|
| +import java.io.IOException;
|
| +import java.lang.Integer;
|
| +import java.lang.Object;
|
| +import java.util.concurrent.locks.ReentrantLock;
|
| +import java.util.Iterator;
|
| +import java.util.List;
|
| +import javax.microedition.khronos.opengles.GL10;
|
| +
|
| +import org.chromium.base.CalledByNative;
|
| +
|
| +public class VideoCapture implements PreviewCallback, OnFrameAvailableListener {
|
| + public class CaptureCapability {
|
| + public int mWidth = 0;
|
| + public int mHeight = 0;
|
| + public int mDesiredFps = 0;
|
| + public int mFpsLowInMs = 0;
|
| + public int mFpsHighInMs = 0;
|
| + }
|
| +
|
| + private Camera mCamera;
|
| + public ReentrantLock mPreviewBufferLock = new ReentrantLock();
|
| + private int mPixelFormat = ImageFormat.YV12;
|
| + private Context mContext = null;
|
| + // True when native code has started capture.
|
| + private boolean mIsRunning=false;
|
| +
|
| + private final int mNumCaptureBuffers = 3;
|
| + private int mExpectedFrameSize = 0;
|
| + private int mId = 0;
|
| + // Native callback context variable.
|
| + private long mNativeCapture = 0;
|
| + private int[] mGlTextures = null;
|
| + private SurfaceTexture mSurfaceTexture = null;
|
| + private static final int GL_TEXTURE_EXTERNAL_OES = 0x8D65;
|
| +
|
| + private int mCameraOrientation = 0;
|
| + private int mCameraFacing = 0;
|
| + private int mDeviceOrientation = 0;
|
| +
|
| + CaptureCapability mCurrentCapability = null;
|
| +
|
| + // Returns an instance of VideoCapture.
|
| + @CalledByNative
|
| + public static VideoCapture createVideoCapture(Context context, int id,
|
| + long nativeCapture) {
|
| + Log.d("JAVA VideoCapture", "createVideoCapture: " + id);
|
| + return new VideoCapture(context, id, nativeCapture);
|
| + }
|
| +
|
| + public VideoCapture(Context context, int id, long nativeCapture) {
|
| + Log.d("JAVA VideoCapture", "VideoCapture constructor: " + id);
|
| + mContext = context;
|
| + mId = id;
|
| + mNativeCapture = nativeCapture;
|
| + }
|
| +
|
| + @CalledByNative
|
| + public int allocate(int width, int height, int frameRate) {
|
| + Log.d("JAVA VideoCapture", "allocate: requested width=" + width +
|
| + ", height=" + height + ", frameRate=" + frameRate);
|
| + try {
|
| + mCamera = Camera.open(mId);
|
| + Camera.CameraInfo camera_info = new Camera.CameraInfo();
|
| + Camera.getCameraInfo(mId, camera_info);
|
| + mCameraOrientation = camera_info.orientation;
|
| + mCameraFacing = camera_info.facing;
|
| + mDeviceOrientation = getDeviceOrientation();
|
| + Log.d("JAVA VideoCapture",
|
| + "allocate: device orientation=" + mDeviceOrientation +
|
| + ", camera orientation=" + mCameraOrientation +
|
| + ", facing=" + mCameraFacing);
|
| +
|
| + Camera.Parameters parameters = mCamera.getParameters();
|
| +
|
| + // Calculate fps.
|
| + List<int[]> listFpsRange = parameters.getSupportedPreviewFpsRange();
|
| + int frameRateInMs = frameRate * 1000;
|
| + boolean fpsIsSupported = false;
|
| + int fpsMin = 0;
|
| + int fpsMax = 0;
|
| + Iterator itFpsRange = listFpsRange.iterator();
|
| + while (itFpsRange.hasNext()) {
|
| + int[] fpsRange = (int[])itFpsRange.next();
|
| + if (fpsRange[0] <= frameRateInMs &&
|
| + frameRateInMs <= fpsRange[1]) {
|
| + fpsIsSupported = true;
|
| + fpsMin = fpsRange[0];
|
| + fpsMax = fpsRange[1];
|
| + break;
|
| + }
|
| + }
|
| +
|
| + if (!fpsIsSupported) {
|
| + Log.e("JAVA VideoCapture", "allocate: fps " + frameRate +
|
| + " is not supported");
|
| + return -1;
|
| + }
|
| +
|
| + mCurrentCapability = new CaptureCapability();
|
| + mCurrentCapability.mFpsLowInMs = fpsMin;
|
| + mCurrentCapability.mFpsHighInMs = fpsMax;
|
| + mCurrentCapability.mDesiredFps = frameRate;
|
| +
|
| + // Calculate size.
|
| + List<Camera.Size> listCameraSize =
|
| + parameters.getSupportedPreviewSizes();
|
| + int minDiff = Integer.MAX_VALUE;
|
| + int matchedWidth = width;
|
| + int matchedHeight = height;
|
| + Iterator itCameraSize = listCameraSize.iterator();
|
| + while (itCameraSize.hasNext()) {
|
| + Camera.Size size = (Camera.Size)itCameraSize.next();
|
| + int diff = Math.abs(size.width - width) +
|
| + Math.abs(size.height - height);
|
| + Log.d("JAVA VideoCapture", "allocate: support resolution (" +
|
| + size.width + ", " + size.height + "), diff=" + diff);
|
| + if (diff < minDiff) {
|
| + minDiff = diff;
|
| + matchedWidth = size.width;
|
| + matchedHeight = size.height;
|
| + }
|
| + }
|
| + mCurrentCapability.mWidth = matchedWidth;
|
| + mCurrentCapability.mHeight = matchedHeight;
|
| + Log.d("JAVA VideoCaptuer", "allocate: matched width=" +
|
| + matchedWidth + ", height=" + matchedHeight);
|
| +
|
| + parameters.setPreviewSize(matchedWidth, matchedHeight);
|
| + parameters.setPreviewFormat(mPixelFormat);
|
| + parameters.setPreviewFpsRange(fpsMin, fpsMax);
|
| + mCamera.setParameters(parameters);
|
| +
|
| + // Set SurfaceTexture.
|
| + mGlTextures = new int[1];
|
| + // Generate one texture pointer and bind it as an external texture.
|
| + GLES20.glGenTextures(1, mGlTextures, 0);
|
| + GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mGlTextures[0]);
|
| + // No mip-mapping with camera source.
|
| + GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES,
|
| + GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
|
| + GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES,
|
| + GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
|
| + // Clamp to edge is only option.
|
| + GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
|
| + GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
|
| + GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
|
| + GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
|
| +
|
| + mSurfaceTexture = new SurfaceTexture(mGlTextures[0]);
|
| + mSurfaceTexture.setOnFrameAvailableListener(this);
|
| +
|
| + mCamera.setPreviewTexture(mSurfaceTexture);
|
| +
|
| + int bufSize = matchedWidth * matchedHeight *
|
| + ImageFormat.getBitsPerPixel(mPixelFormat) / 8;
|
| + for (int i = 0; i < mNumCaptureBuffers; i++) {
|
| + byte[] buffer = new byte[bufSize];
|
| + mCamera.addCallbackBuffer(buffer);
|
| + }
|
| + mExpectedFrameSize = bufSize;
|
| + }
|
| + catch (Exception ex) {
|
| + return -1;
|
| + }
|
| + return 0;
|
| + }
|
| +
|
| + @CalledByNative
|
| + public int queryWidth() {
|
| + return mCurrentCapability.mWidth;
|
| + }
|
| +
|
| + @CalledByNative
|
| + public int queryHeight() {
|
| + return mCurrentCapability.mHeight;
|
| + }
|
| +
|
| + @CalledByNative
|
| + public int queryFrameRate() {
|
| + return mCurrentCapability.mDesiredFps;
|
| + }
|
| +
|
| + @CalledByNative
|
| + public int startCapture() {
|
| + Log.d("JAVA VideoCapture", "startCapture:");
|
| + if (mCamera == null) {
|
| + Log.e("JAVA VideoCapture", "startCapture: camera is null");
|
| + return -1;
|
| + }
|
| +
|
| + try {
|
| + mPreviewBufferLock.lock();
|
| + if (mIsRunning) {
|
| + return 0;
|
| + }
|
| + mIsRunning = true;
|
| + mPreviewBufferLock.unlock();
|
| + mCamera.setPreviewCallbackWithBuffer(this);
|
| + mCamera.startPreview();
|
| + }
|
| + catch (Exception ex) {
|
| + Log.e("JAVA VideoCapture", "startCapture: failed to start camera");
|
| + return -1;
|
| + }
|
| + return 0;
|
| + }
|
| +
|
| + @CalledByNative
|
| + public int stopCapture() {
|
| + Log.d("JAVA VideoCapture", "stopCapture:");
|
| + if (mCamera == null) {
|
| + Log.d("JAVA VideoCapture", "stopCapture: camera is null");
|
| + return 0;
|
| + }
|
| +
|
| + try {
|
| + mPreviewBufferLock.lock();
|
| + if (!mIsRunning) {
|
| + return 0;
|
| + }
|
| + mIsRunning = false;
|
| + mPreviewBufferLock.unlock();
|
| +
|
| + mCamera.stopPreview();
|
| + mCamera.setPreviewCallbackWithBuffer(null);
|
| + }
|
| + catch (Exception ex) {
|
| + Log.e("JAVA VideoCapture", "stopCapture: failed to stop camera");
|
| + return -1;
|
| + }
|
| +
|
| + return 0;
|
| + }
|
| +
|
| + @CalledByNative
|
| + public void deallocate() {
|
| + Log.d("JAVA VideoCapture", "deallocate:");
|
| + if (mCamera == null)
|
| + return;
|
| +
|
| + stopCapture();
|
| + try {
|
| + mCamera.setPreviewTexture(null);
|
| + mSurfaceTexture.setOnFrameAvailableListener(null);
|
| + GLES20.glDeleteTextures(1, mGlTextures, 0);
|
| + mCurrentCapability = null;
|
| + mCamera.release();
|
| + mCamera = null;
|
| + }
|
| + catch (Exception ex) {
|
| + Log.e("JAVA VideoCapture", "deallocate: failed to deallocate camera");
|
| + return;
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void onPreviewFrame(byte[] data, Camera camera) {
|
| + mPreviewBufferLock.lock();
|
| +
|
| + if (mIsRunning) {
|
| + if (data.length == mExpectedFrameSize) {
|
| + int rotation = getDeviceOrientation();
|
| + if (rotation != mDeviceOrientation) {
|
| + mDeviceOrientation = rotation;
|
| + Log.d("JAVA VideoCapture",
|
| + "onPreviewFrame: device orientation=" +
|
| + mDeviceOrientation + ", camera orientation=" +
|
| + mCameraOrientation);
|
| + }
|
| + boolean flip_vert = false;
|
| + boolean flip_horiz = false;
|
| + if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
| + rotation = (mCameraOrientation + rotation) % 360;
|
| + rotation = (360 - rotation) % 360;
|
| + flip_horiz = (rotation == 180 || rotation == 0);
|
| + flip_vert = !flip_horiz;
|
| + } else {
|
| + rotation = (mCameraOrientation - rotation + 360) % 360;
|
| + }
|
| + nativeOnFrameAvailable(data, mExpectedFrameSize, mNativeCapture,
|
| + rotation, flip_vert, flip_horiz);
|
| + if (camera != null) {
|
| + camera.addCallbackBuffer(data);
|
| + }
|
| + }
|
| + }
|
| + mPreviewBufferLock.unlock();
|
| + }
|
| +
|
| + @Override
|
| + public void onFrameAvailable(SurfaceTexture surfaceTexture) { }
|
| +
|
| + private native void nativeOnFrameAvailable(byte[] data, int length,
|
| + long nativeCapture, int rotation,
|
| + boolean flip_vert, boolean flip_horiz);
|
| +
|
| + private int getDeviceOrientation() {
|
| + int orientation = 0;
|
| + if (mContext != null) {
|
| + WindowManager wm = (WindowManager)mContext.getSystemService(
|
| + Context.WINDOW_SERVICE);
|
| + switch(wm.getDefaultDisplay().getRotation()) {
|
| + case Surface.ROTATION_0:
|
| + orientation = 0;
|
| + break;
|
| + case Surface.ROTATION_90:
|
| + orientation = 90;
|
| + break;
|
| + case Surface.ROTATION_180:
|
| + orientation = 180;
|
| + break;
|
| + case Surface.ROTATION_270:
|
| + orientation = 270;
|
| + break;
|
| + }
|
| + }
|
| + return orientation;
|
| + }
|
| +}
|
|
|