Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(612)

Unified Diff: media/base/android/java/src/org/chromium/media/VideoCapture.java

Issue 11860002: Add video capture on Android. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: catch only IOException Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | media/base/android/media_jni_registrar.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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,338 @@
+// 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.GLES20;
+import android.util.Log;
+import android.view.Surface;
+import android.view.WindowManager;
+
+import java.io.IOException;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.Iterator;
+import java.util.List;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+
+@JNINamespace("media")
+public class VideoCapture implements PreviewCallback, OnFrameAvailableListener {
+ static class CaptureCapability {
+ public int mWidth = 0;
+ public int mHeight = 0;
+ public int mDesiredFps = 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 static final int NUM_CAPTURE_BUFFERS = 3;
+ private int mExpectedFrameSize = 0;
+ private int mId = 0;
+ // Native callback context variable.
+ private int mNativeVideoCaptureDeviceAndroid = 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;
+ private static final String TAG = "VideoCapture";
+
+ @CalledByNative
+ public static VideoCapture createVideoCapture(
+ Context context, int id, int nativeVideoCaptureDeviceAndroid) {
+ return new VideoCapture(context, id, nativeVideoCaptureDeviceAndroid);
+ }
+
+ public VideoCapture(
+ Context context, int id, int nativeVideoCaptureDeviceAndroid) {
+ mContext = context;
+ mId = id;
+ mNativeVideoCaptureDeviceAndroid = nativeVideoCaptureDeviceAndroid;
+ }
+
+ // Returns true on success, false otherwise.
+ @CalledByNative
+ public boolean allocate(int width, int height, int frameRate) {
+ Log.d(TAG, "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(TAG, "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(TAG, "allocate: fps " + frameRate + " is not supported");
+ return false;
+ }
+
+ mCurrentCapability = new CaptureCapability();
+ 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(TAG, "allocate: support resolution (" +
+ size.width + ", " + size.height + "), diff=" + diff);
+ // TODO(wjia): Remove this hack (forcing width to be multiple
+ // of 32) by supporting stride in video frame buffer.
+ // Right now, VideoCaptureController requires compact YV12
+ // (i.e., with no padding).
+ if (diff < minDiff && (size.width % 32 == 0)) {
+ minDiff = diff;
+ matchedWidth = size.width;
+ matchedHeight = size.height;
+ }
+ }
+ if (minDiff == Integer.MAX_VALUE) {
+ Log.e(TAG, "allocate: can not find a resolution whose width is multiple of 32");
+ return false;
+ }
+ mCurrentCapability.mWidth = matchedWidth;
+ mCurrentCapability.mHeight = matchedHeight;
+ Log.d(TAG, "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,
+ GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
+ GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES,
+ GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+ // Clamp to edge is only option.
+ GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
+ GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+ GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
+ GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+
+ mSurfaceTexture = new SurfaceTexture(mGlTextures[0]);
+ mSurfaceTexture.setOnFrameAvailableListener(null);
+
+ mCamera.setPreviewTexture(mSurfaceTexture);
+
+ int bufSize = matchedWidth * matchedHeight *
+ ImageFormat.getBitsPerPixel(mPixelFormat) / 8;
+ for (int i = 0; i < NUM_CAPTURE_BUFFERS; i++) {
+ byte[] buffer = new byte[bufSize];
+ mCamera.addCallbackBuffer(buffer);
+ }
+ mExpectedFrameSize = bufSize;
+ } catch (IOException ex) {
+ Log.e(TAG, "allocate: " + ex);
+ return false;
+ }
+
+ return true;
+ }
+
+ @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() {
+ if (mCamera == null) {
+ Log.e(TAG, "startCapture: camera is null");
+ return -1;
+ }
+
+ mPreviewBufferLock.lock();
+ try {
+ if (mIsRunning) {
+ return 0;
+ }
+ mIsRunning = true;
+ } finally {
+ mPreviewBufferLock.unlock();
+ }
+ mCamera.setPreviewCallbackWithBuffer(this);
+ mCamera.startPreview();
+ return 0;
+ }
+
+ @CalledByNative
+ public int stopCapture() {
+ if (mCamera == null) {
+ Log.e(TAG, "stopCapture: camera is null");
+ return 0;
+ }
+
+ mPreviewBufferLock.lock();
+ try {
+ if (!mIsRunning) {
+ return 0;
+ }
+ mIsRunning = false;
+ } finally {
+ mPreviewBufferLock.unlock();
+ }
+
+ mCamera.stopPreview();
+ mCamera.setPreviewCallbackWithBuffer(null);
+ return 0;
+ }
+
+ @CalledByNative
+ public void 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 (IOException ex) {
+ Log.e(TAG, "deallocate: failed to deallocate camera, " + ex);
+ return;
+ }
+ }
+
+ @Override
+ public void onPreviewFrame(byte[] data, Camera camera) {
+ mPreviewBufferLock.lock();
+ try {
+ if (!mIsRunning) {
+ return;
+ }
+ if (data.length == mExpectedFrameSize) {
+ int rotation = getDeviceOrientation();
+ if (rotation != mDeviceOrientation) {
+ mDeviceOrientation = rotation;
+ Log.d(TAG,
+ "onPreviewFrame: device orientation=" +
+ mDeviceOrientation + ", camera orientation=" +
+ mCameraOrientation);
+ }
+ boolean flipVertical = false;
+ boolean flipHorizontal = false;
+ if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+ rotation = (mCameraOrientation + rotation) % 360;
+ rotation = (360 - rotation) % 360;
+ flipHorizontal = (rotation == 180 || rotation == 0);
+ flipVertical = !flipHorizontal;
+ } else {
+ rotation = (mCameraOrientation - rotation + 360) % 360;
+ }
+ nativeOnFrameAvailable(mNativeVideoCaptureDeviceAndroid,
+ data, mExpectedFrameSize,
+ rotation, flipVertical, flipHorizontal);
+ }
+ } finally {
+ mPreviewBufferLock.unlock();
+ if (camera != null) {
+ camera.addCallbackBuffer(data);
+ }
+ }
+ }
+
+ // TODO(wjia): investigate whether reading from texture could give better
+ // performance and frame rate.
+ @Override
+ public void onFrameAvailable(SurfaceTexture surfaceTexture) { }
+
+ private native void nativeOnFrameAvailable(
+ int nativeVideoCaptureDeviceAndroid,
+ byte[] data,
+ int length,
+ int rotation,
+ boolean flipVertical,
+ boolean flipHorizontal);
+
+ private int getDeviceOrientation() {
+ int orientation = 0;
+ if (mContext != null) {
+ WindowManager wm = (WindowManager)mContext.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;
+ }
+}
« no previous file with comments | « no previous file | media/base/android/media_jni_registrar.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698