blob: bdd999dc8a16367912bafffc9fbb7344ae8cdae4 [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.hardware.display.DisplayManager;
import android.view.Display;
import android.view.Surface;
import org.chromium.base.ContextUtils;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
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 necessary 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 int mId;
// Native callback context variable.
protected final long mNativeVideoCaptureDeviceAndroid;
protected boolean mUseBackgroundThreadForTesting;
VideoCapture(int id, long nativeVideoCaptureDeviceAndroid) {
mId = id;
mNativeVideoCaptureDeviceAndroid = nativeVideoCaptureDeviceAndroid;
}
// Allocate necessary resources for capture.
@CalledByNative
public abstract boolean allocate(
int width, int height, int frameRate, boolean enableFaceDetection);
// Success is indicated by returning true and a callback to
// VideoCaptureJni.get().onStarted(, VideoCapture.this), which may occur synchronously or
// asynchronously. Failure can be indicated by one of the following:
// * Returning false. In this case no callback to VideoCaptureJni.get().onStarted() is made.
// * Returning true, and asynchronously invoking VideoCaptureJni.get().onError. In this case
// also no callback to VideoCaptureJni.get().onStarted() is made.
@CalledByNative
public abstract boolean startCaptureMaybeAsync();
// Blocks until it is guaranteed that no more frames are sent.
@CalledByNative
public abstract boolean stopCaptureAndBlockUntilStopped();
// Replies by calling VideoCaptureJni.get().onGetPhotoCapabilitiesReply(). Will pass |null|
// for parameter |result| to indicate failure.
@CalledByNative
public abstract void getPhotoCapabilitiesAsync(long callbackId);
/**
* @param zoom Zoom level, should be ignored if 0.
* @param focusMode Focus mode following AndroidMeteringMode enum.
* @param focusDistance Desired distance to plane of sharpest focus.
* @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 exposureTime Duration each pixel is exposed to light (in nanoseconds).
* @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, double focusDistance,
int exposureMode, double width, double height, double[] pointsOfInterest2D,
boolean hasExposureCompensation, double exposureCompensation, double exposureTime,
int whiteBalanceMode, double iso, boolean hasRedEyeReduction, boolean redEyeReduction,
int fillLightMode, boolean hasTorch, boolean torch, double colorTemperature);
// Replies by calling VideoCaptureJni.get().onPhotoTaken().
@CalledByNative
public abstract void takePhotoAsync(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() {
final int orientation;
DisplayManager dm = (DisplayManager) ContextUtils.getApplicationContext().getSystemService(
Context.DISPLAY_SERVICE);
switch (dm.getDisplay(Display.DEFAULT_DISPLAY).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;
}
// {@link VideoCaptureJni.get().onPhotoTaken()} needs to be called back if there's any
// problem after {@link takePhotoAsync()} has returned true.
protected void notifyTakePhotoError(long callbackId) {
VideoCaptureJni.get().onPhotoTaken(
mNativeVideoCaptureDeviceAndroid, VideoCapture.this, callbackId, null);
}
/**
* 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;
}
@NativeMethods
interface Natives {
// Method for VideoCapture implementations to call back native code.
void onFrameAvailable(long nativeVideoCaptureDeviceAndroid, VideoCapture caller,
byte[] data, int length, int rotation);
void onI420FrameAvailable(long nativeVideoCaptureDeviceAndroid, VideoCapture caller,
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.
void onError(long nativeVideoCaptureDeviceAndroid, VideoCapture caller,
int androidVideoCaptureError, String message);
// Method for VideoCapture implementations to signal that a frame was dropped.
void onFrameDropped(long nativeVideoCaptureDeviceAndroid, VideoCapture caller,
int androidVideoCaptureFrameDropReason);
void onGetPhotoCapabilitiesReply(long nativeVideoCaptureDeviceAndroid, VideoCapture caller,
long callbackId, PhotoCapabilities result);
// Callback for calls to takePhoto(). This can indicate both success and
// failure. Failure is indicated by |data| being null.
void onPhotoTaken(long nativeVideoCaptureDeviceAndroid, VideoCapture caller,
long callbackId, byte[] data);
// Method for VideoCapture implementations to report device started event.
void onStarted(long nativeVideoCaptureDeviceAndroid, VideoCapture caller);
void dCheckCurrentlyOnIncomingTaskRunner(
long nativeVideoCaptureDeviceAndroid, VideoCapture caller);
}
}