Chromium Code Reviews| 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,329 @@ |
| +// 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]; |
|
qinmin
2013/01/22 21:31:42
indent by 4
wjia(left Chromium)
2013/01/22 22:40:01
Done.
|
| + 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) { |
|
zhongping.wang
2013/01/23 05:09:24
not unlock mPreviewBufferLock
wjia(left Chromium)
2013/01/24 05:39:16
Switched to use try{} finally{}.
|
| + 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) { |
|
zhongping.wang
2013/01/23 05:09:24
not unlock mPreviewBufferLock
wjia(left Chromium)
2013/01/24 05:39:16
Done.
|
| + 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; |
|
qinmin
2013/01/22 21:31:42
indent by 4
wjia(left Chromium)
2013/01/22 22:40:01
Done.
|
| + 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) { } |
|
zhongping.wang
2013/01/23 05:09:24
Is interface OnFrameAvailableListener necessary si
wjia(left Chromium)
2013/01/24 05:39:16
Done.
|
| + |
| + 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); |
|
qinmin
2013/01/22 21:31:42
indent by 8
wjia(left Chromium)
2013/01/22 22:40:01
Done.
|
| + switch(wm.getDefaultDisplay().getRotation()) { |
| + case Surface.ROTATION_0 : orientation = 0 ; break; |
|
qinmin
2013/01/22 21:31:42
use new lines
wjia(left Chromium)
2013/01/22 22:40:01
Done.
|
| + case Surface.ROTATION_90 : orientation = 90 ; break; |
| + case Surface.ROTATION_180: orientation = 180; break; |
| + case Surface.ROTATION_270: orientation = 270; break; |
| + } |
| + } |
| + return orientation; |
| + } |
| +} |