blob: 9cd599fb8b7de76d75a7d0e442ddbacf039535f5 [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.ImageFormat;
import android.view.Surface;
import android.view.WindowManager;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Video Capture Device base class, defines a set of methods that native code
* needs to use to configure, start capture, and to be reached by callbacks and
* provides some neccesary data type(s) with accessors.
**/
@JNINamespace("media")
public abstract class VideoCapture {
/**
* Common class for storing a framerate range. Values should be multiplied by 1000.
*/
protected static class FramerateRange {
public int min;
public int max;
public FramerateRange(int min, int max) {
this.min = min;
this.max = max;
}
}
// The angle (0, 90, 180, 270) that the image needs to be rotated to show in
// the display's native orientation.
protected int mCameraNativeOrientation;
// In some occasions we need to invert the device rotation readings, see the
// individual implementations.
protected boolean mInvertDeviceOrientationReadings;
protected VideoCaptureFormat mCaptureFormat;
protected final Context mContext;
protected final int mId;
// Native callback context variable.
protected final long mNativeVideoCaptureDeviceAndroid;
protected boolean mUseBackgroundThreadForTesting;
VideoCapture(Context context, int id, long nativeVideoCaptureDeviceAndroid) {
mContext = context;
mId = id;
mNativeVideoCaptureDeviceAndroid = nativeVideoCaptureDeviceAndroid;
}
// Allocate necessary resources for capture.
@CalledByNative
public abstract boolean allocate(int width, int height, int frameRate);
@CalledByNative
public abstract boolean startCapture();
@CalledByNative
public abstract boolean stopCapture();
@CalledByNative
public abstract PhotoCapabilities getPhotoCapabilities();
/**
* @param zoom Zoom level, should be ignored if 0.
* @param focusMode Focus mode following AndroidMeteringMode enum.
* @param exposureMode Exposure mode following AndroidMeteringMode enum.
* @param pointsOfInterest2D 2D normalized points of interest, marshalled with
* x coordinate first followed by the y coordinate.
* @param hasExposureCompensation Indicates if |exposureCompensation| is set.
* @param exposureCompensation Adjustment to auto exposure. 0 means not adjusted.
* @param whiteBalanceMode White Balance mode following AndroidMeteringMode enum.
* @param iso Sensitivity to light. 0, which would be invalid, means ignore.
* @param hasRedEyeReduction Indicates if |redEyeReduction| is set.
* @param redEyeReduction Value of red eye reduction for the auto flash setting.
* @param fillLightMode Flash setting, following AndroidFillLightMode enum.
* @param colorTemperature White Balance reference temperature, valid if whiteBalanceMode is
* manual, and its value is larger than 0.
* @param torch Torch setting, true meaning on.
*/
@CalledByNative
public abstract void setPhotoOptions(double zoom, int focusMode, int exposureMode, double width,
double height, float[] pointsOfInterest2D, boolean hasExposureCompensation,
double exposureCompensation, int whiteBalanceMode, double iso,
boolean hasRedEyeReduction, boolean redEyeReduction, int fillLightMode,
boolean hasTorch, boolean torch, double colorTemperature);
@CalledByNative
public abstract boolean takePhoto(final long callbackId);
@CalledByNative
public abstract void deallocate();
@CalledByNative
public final int queryWidth() {
return mCaptureFormat.mWidth;
}
@CalledByNative
public final int queryHeight() {
return mCaptureFormat.mHeight;
}
@CalledByNative
public final int queryFrameRate() {
return mCaptureFormat.mFramerate;
}
@CalledByNative
public final int getColorspace() {
switch (mCaptureFormat.mPixelFormat) {
case ImageFormat.YV12:
return AndroidImageFormat.YV12;
case ImageFormat.YUV_420_888:
return AndroidImageFormat.YUV_420_888;
case ImageFormat.NV21:
return AndroidImageFormat.NV21;
case ImageFormat.UNKNOWN:
default:
return AndroidImageFormat.UNKNOWN;
}
}
@CalledByNative
public final void setTestMode() {
mUseBackgroundThreadForTesting = true;
}
protected final int getCameraRotation() {
int rotation = mInvertDeviceOrientationReadings ? (360 - getDeviceRotation())
: getDeviceRotation();
return (mCameraNativeOrientation + rotation) % 360;
}
protected final int getDeviceRotation() {
if (mContext == null) return 0;
final int orientation;
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;
}
/**
* Finds the framerate range matching |targetFramerate|. Tries to find a range with as low of a
* minimum value as possible to allow the camera adjust based on the lighting conditions.
* Assumes that all framerate values are multiplied by 1000.
*
* This code is mostly copied from WebRTC:
* CameraEnumerationAndroid.getClosestSupportedFramerateRange
* in webrtc/api/android/java/src/org/webrtc/CameraEnumerationAndroid.java
*/
protected static FramerateRange getClosestFramerateRange(
final List<FramerateRange> framerateRanges, final int targetFramerate) {
return Collections.min(framerateRanges, new Comparator<FramerateRange>() {
// Threshold and penalty weights if the upper bound is further away than
// |MAX_FPS_DIFF_THRESHOLD| from requested.
private static final int MAX_FPS_DIFF_THRESHOLD = 5000;
private static final int MAX_FPS_LOW_DIFF_WEIGHT = 1;
private static final int MAX_FPS_HIGH_DIFF_WEIGHT = 3;
// Threshold and penalty weights if the lower bound is bigger than |MIN_FPS_THRESHOLD|.
private static final int MIN_FPS_THRESHOLD = 8000;
private static final int MIN_FPS_LOW_VALUE_WEIGHT = 1;
private static final int MIN_FPS_HIGH_VALUE_WEIGHT = 4;
// Use one weight for small |value| less than |threshold|, and another weight above.
private int progressivePenalty(
int value, int threshold, int lowWeight, int highWeight) {
return (value < threshold)
? value * lowWeight
: threshold * lowWeight + (value - threshold) * highWeight;
}
int diff(FramerateRange range) {
final int minFpsError = progressivePenalty(range.min, MIN_FPS_THRESHOLD,
MIN_FPS_LOW_VALUE_WEIGHT, MIN_FPS_HIGH_VALUE_WEIGHT);
final int maxFpsError = progressivePenalty(Math.abs(targetFramerate - range.max),
MAX_FPS_DIFF_THRESHOLD, MAX_FPS_LOW_DIFF_WEIGHT, MAX_FPS_HIGH_DIFF_WEIGHT);
return minFpsError + maxFpsError;
}
@Override
public int compare(FramerateRange range1, FramerateRange range2) {
return diff(range1) - diff(range2);
}
});
}
protected static int[] integerArrayListToArray(ArrayList<Integer> intArrayList) {
int[] intArray = new int[intArrayList.size()];
for (int i = 0; i < intArrayList.size(); i++) {
intArray[i] = intArrayList.get(i).intValue();
}
return intArray;
}
// Method for VideoCapture implementations to call back native code.
public native void nativeOnFrameAvailable(
long nativeVideoCaptureDeviceAndroid, byte[] data, int length, int rotation);
public native void nativeOnI420FrameAvailable(long nativeVideoCaptureDeviceAndroid,
ByteBuffer yBuffer, int yStride, ByteBuffer uBuffer, ByteBuffer vBuffer,
int uvRowStride, int uvPixelStride, int width, int height, int rotation,
long timestamp);
// Method for VideoCapture implementations to signal an asynchronous error.
public native void nativeOnError(long nativeVideoCaptureDeviceAndroid, String message);
// Method for VideoCapture implementations to send Photos back to.
public native void nativeOnPhotoTaken(
long nativeVideoCaptureDeviceAndroid, long callbackId, byte[] data);
// Method for VideoCapture implementations to report device started event.
public native void nativeOnStarted(long nativeVideoCaptureDeviceAndroid);
}