blob: b148129362df27d1a31a119dcc5bb387021841fa [file] [log] [blame]
// Copyright 2014 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.SurfaceTexture;
import android.opengl.GLES20;
import android.util.Log;
import android.view.Surface;
import android.view.WindowManager;
import org.chromium.base.JNINamespace;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
/**
* Video Capture Device extension of VideoCapture to provide common functionality
* for capture using android.hardware.Camera API (deprecated in API 21). Normal
* Android and Tango devices are extensions of this class.
**/
@JNINamespace("media")
@SuppressWarnings("deprecation")
public abstract class VideoCaptureCamera extends VideoCapture
implements android.hardware.Camera.PreviewCallback {
protected android.hardware.Camera mCamera;
// Lock to mutually exclude execution of OnPreviewFrame() and {start/stop}Capture().
protected ReentrantLock mPreviewBufferLock = new ReentrantLock();
// True when native code has started capture.
protected boolean mIsRunning = false;
protected int[] mGlTextures = null;
protected SurfaceTexture mSurfaceTexture = null;
protected static final int GL_TEXTURE_EXTERNAL_OES = 0x8D65;
protected int mCameraOrientation;
protected int mCameraFacing;
protected int mDeviceOrientation;
private static final String TAG = "VideoCaptureCamera";
protected static android.hardware.Camera.CameraInfo getCameraInfo(int id) {
android.hardware.Camera.CameraInfo cameraInfo =
new android.hardware.Camera.CameraInfo();
try {
android.hardware.Camera.getCameraInfo(id, cameraInfo);
} catch (RuntimeException ex) {
Log.e(TAG, "getCameraInfo: Camera.getCameraInfo: " + ex);
return null;
}
return cameraInfo;
}
protected static android.hardware.Camera.Parameters getCameraParameters(
android.hardware.Camera camera) {
android.hardware.Camera.Parameters parameters;
try {
parameters = camera.getParameters();
} catch (RuntimeException ex) {
Log.e(TAG, "getCameraParameters: android.hardware.Camera.getParameters: " + ex);
camera.release();
return null;
}
return parameters;
}
VideoCaptureCamera(Context context,
int id,
long nativeVideoCaptureDeviceAndroid) {
super(context, id, nativeVideoCaptureDeviceAndroid);
}
@Override
public boolean allocate(int width, int height, int frameRate) {
Log.d(TAG, "allocate: requested (" + width + "x" + height + ")@"
+ frameRate + "fps");
try {
mCamera = android.hardware.Camera.open(mId);
} catch (RuntimeException ex) {
Log.e(TAG, "allocate: Camera.open: " + ex);
return false;
}
android.hardware.Camera.CameraInfo cameraInfo = VideoCaptureCamera.getCameraInfo(mId);
if (cameraInfo == null) {
mCamera.release();
mCamera = null;
return false;
}
mCameraOrientation = cameraInfo.orientation;
mCameraFacing = cameraInfo.facing;
mDeviceOrientation = getDeviceOrientation();
Log.d(TAG, "allocate: orientation dev=" + mDeviceOrientation
+ ", cam=" + mCameraOrientation + ", facing=" + mCameraFacing);
android.hardware.Camera.Parameters parameters = getCameraParameters(mCamera);
if (parameters == null) {
mCamera = null;
return false;
}
// getSupportedPreviewFpsRange() returns a List with at least one
// element, but when camera is in bad state, it can return null pointer.
List<int[]> listFpsRange = parameters.getSupportedPreviewFpsRange();
if (listFpsRange == null || listFpsRange.size() == 0) {
Log.e(TAG, "allocate: no fps range found");
return false;
}
// API fps ranges are scaled up x1000 to avoid floating point.
int frameRateScaled = frameRate * 1000;
// Use the first range as the default chosen range.
int[] chosenFpsRange = listFpsRange.get(0);
int frameRateNearest = Math.abs(frameRateScaled - chosenFpsRange[0])
< Math.abs(frameRateScaled - chosenFpsRange[1])
? chosenFpsRange[0] : chosenFpsRange[1];
int chosenFrameRate = (frameRateNearest + 999) / 1000;
int fpsRangeSize = Integer.MAX_VALUE;
for (int[] fpsRange : listFpsRange) {
if (fpsRange[0] <= frameRateScaled && frameRateScaled <= fpsRange[1]
&& (fpsRange[1] - fpsRange[0]) <= fpsRangeSize) {
chosenFpsRange = fpsRange;
chosenFrameRate = frameRate;
fpsRangeSize = fpsRange[1] - fpsRange[0];
}
}
Log.d(TAG, "allocate: fps set to " + chosenFrameRate + ", ["
+ chosenFpsRange[0] + "-" + chosenFpsRange[1] + "]");
// Calculate size.
List<android.hardware.Camera.Size> listCameraSize =
parameters.getSupportedPreviewSizes();
int minDiff = Integer.MAX_VALUE;
int matchedWidth = width;
int matchedHeight = height;
for (android.hardware.Camera.Size size : listCameraSize) {
int diff = Math.abs(size.width - width)
+ Math.abs(size.height - height);
Log.d(TAG, "allocate: supported ("
+ 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 multiple-of-32 resolution");
return false;
}
Log.d(TAG, "allocate: matched (" + matchedWidth + "x" + matchedHeight + ")");
if (parameters.isVideoStabilizationSupported()) {
Log.d(TAG, "Image stabilization supported, currently: "
+ parameters.getVideoStabilization() + ", setting it.");
parameters.setVideoStabilization(true);
} else {
Log.d(TAG, "Image stabilization not supported.");
}
setCaptureParameters(matchedWidth, matchedHeight, chosenFrameRate, parameters);
parameters.setPictureSize(matchedWidth, matchedHeight);
parameters.setPreviewSize(matchedWidth, matchedHeight);
parameters.setPreviewFpsRange(chosenFpsRange[0], chosenFpsRange[1]);
parameters.setPreviewFormat(mCaptureFormat.mPixelFormat);
parameters.setFocusMode(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
try {
mCamera.setParameters(parameters);
} catch (RuntimeException ex) {
Log.e(TAG, "setParameters: " + ex);
return false;
}
// Set SurfaceTexture. Android Capture needs a SurfaceTexture even if
// it is not going to be used.
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);
try {
mCamera.setPreviewTexture(mSurfaceTexture);
} catch (IOException ex) {
Log.e(TAG, "allocate: " + ex);
return false;
}
allocateBuffers();
return true;
}
@Override
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();
}
setPreviewCallback(this);
try {
mCamera.startPreview();
} catch (RuntimeException ex) {
Log.e(TAG, "startCapture: Camera.startPreview: " + ex);
return -1;
}
return 0;
}
@Override
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();
setPreviewCallback(null);
return 0;
}
@Override
public void deallocate() {
if (mCamera == null) return;
stopCapture();
try {
mCamera.setPreviewTexture(null);
if (mGlTextures != null)
GLES20.glDeleteTextures(1, mGlTextures, 0);
mCaptureFormat = null;
mCamera.release();
mCamera = null;
} catch (IOException ex) {
Log.e(TAG, "deallocate: failed to deallocate camera, " + ex);
return;
}
}
// Local hook to allow derived classes to fill capture format and modify
// camera parameters as they see fit.
abstract void setCaptureParameters(
int width,
int height,
int frameRate,
android.hardware.Camera.Parameters cameraParameters);
// Local method to be overriden with the particular setPreviewCallback to be
// used in the implementations.
abstract void setPreviewCallback(android.hardware.Camera.PreviewCallback cb);
protected 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;
}
}