/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.hardware.camera2.legacy;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.FaceDetectionListener;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.legacy.ParameterUtils.ZoomData;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.params.Face;
import android.hardware.camera2.utils.ListUtils;
import android.hardware.camera2.utils.ParamsUtils;
import android.util.Log;
import android.util.Size;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
import java.util.List;
import static android.hardware.camera2.CaptureRequest.*;
import static com.android.internal.util.Preconditions.*;
/**
* Map legacy face detect callbacks into face detection results.
*/
@SuppressWarnings("deprecation")
public class LegacyFaceDetectMapper {
private static String TAG = "LegacyFaceDetectMapper";
private static final boolean DEBUG = false;
private final Camera mCamera;
/** Is the camera capable of face detection? */
private final boolean mFaceDetectSupported;
/** Is the camera is running face detection? */
private boolean mFaceDetectEnabled = false;
/** Did the last request say to use SCENE_MODE = FACE_PRIORITY? */
private boolean mFaceDetectScenePriority = false;
/** Did the last request enable the face detect mode to ON? */
private boolean mFaceDetectReporting = false;
/** Synchronize access to all fields */
private final Object mLock = new Object();
private Camera.Face[] mFaces;
private Camera.Face[] mFacesPrev;
/**
* Instantiate a new face detect mapper.
*
* @param camera a non-{@code null} camera1 device
* @param characteristics a non-{@code null} camera characteristics for that camera1
*
* @throws NullPointerException if any of the args were {@code null}
*/
public LegacyFaceDetectMapper(Camera camera, CameraCharacteristics characteristics) {
mCamera = checkNotNull(camera, "camera must not be null");
checkNotNull(characteristics, "characteristics must not be null");
mFaceDetectSupported = ArrayUtils.contains(
characteristics.get(
CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES),
STATISTICS_FACE_DETECT_MODE_SIMPLE);
if (!mFaceDetectSupported) {
return;
}
mCamera.setFaceDetectionListener(new FaceDetectionListener() {
@Override
public void onFaceDetection(Camera.Face[] faces, Camera camera) {
int lengthFaces = faces == null ? 0 : faces.length;
synchronized (mLock) {
if (mFaceDetectEnabled) {
mFaces = faces;
} else if (lengthFaces > 0) {
// stopFaceDetectMode could race against the requests, print a debug log
Log.d(TAG,
"onFaceDetection - Ignored some incoming faces since" +
"face detection was disabled");
}
}
if (DEBUG) {
Log.v(TAG, "onFaceDetection - read " + lengthFaces + " faces");
}
}
});
}
/**
* Process the face detect mode from the capture request into an api1 face detect toggle.
*
* <p>This method should be called after the parameters are {@link LegacyRequestMapper mapped}
* with the request.</p>
*
* <p>Callbacks are processed in the background, and the next call to {@link #mapResultTriggers}
* will have the latest faces detected as reflected by the camera1 callbacks.</p>
*
* <p>None of the arguments will be mutated.</p>
*
* @param captureRequest a non-{@code null} request
* @param parameters a non-{@code null} parameters corresponding to this request (read-only)
*/
public void processFaceDetectMode(CaptureRequest captureRequest,
Camera.Parameters parameters) {
checkNotNull(captureRequest, "captureRequest must not be null");
/*
* statistics.faceDetectMode
*/
int fdMode = ParamsUtils.getOrDefault(captureRequest, STATISTICS_FACE_DETECT_MODE,
STATISTICS_FACE_DETECT_MODE_OFF);
if (fdMode != STATISTICS_FACE_DETECT_MODE_OFF && !mFaceDetectSupported) {
Log.w(TAG,
"processFaceDetectMode - Ignoring statistics.faceDetectMode; " +
"face detection is not available");
return;
}
/*
* control.sceneMode
*/
int sceneMode = ParamsUtils.getOrDefault(captureRequest, CONTROL_SCENE_MODE,
CONTROL_SCENE_MODE_DISABLED);
if (sceneMode == CONTROL_SCENE_MODE_FACE_PRIORITY && !mFaceDetectSupported) {
Log.w(TAG, "processFaceDetectMode - ignoring control.sceneMode == FACE_PRIORITY; " +
"face detection is not available");
return;
}
// Print some warnings out in case the values were wrong
switch (fdMode) {
case STATISTICS_FACE_DETECT_MODE_OFF:
case STATISTICS_FACE_DETECT_MODE_SIMPLE:
break;
case STATISTICS_FACE_DETECT_MODE_FULL:
Log.w(TAG,
"processFaceDetectMode - statistics.faceDetectMode == FULL unsupported, " +
"downgrading to SIMPLE");
break;
default:
Log.w(TAG, "processFaceDetectMode - ignoring unknown statistics.faceDetectMode = "
+ fdMode);
return;
}
boolean enableFaceDetect = (fdMode != STATISTICS_FACE_DETECT_MODE_OFF)
|| (sceneMode == CONTROL_SCENE_MODE_FACE_PRIORITY);
synchronized (mLock) {
// Enable/disable face detection if it's changed since last time
if (enableFaceDetect != mFaceDetectEnabled) {
if (enableFaceDetect) {
mCamera.startFaceDetection();
if (DEBUG) {
Log.v(TAG, "processFaceDetectMode - start face detection");
}
} else {
mCamera.stopFaceDetection();
if (DEBUG) {
Log.v(TAG, "processFaceDetectMode - stop face detection");
}
mFaces = null;
}
mFaceDetectEnabled = enableFaceDetect;
mFaceDetectScenePriority = sceneMode == CONTROL_SCENE_MODE_FACE_PRIORITY;
mFaceDetectReporting = fdMode != STATISTICS_FACE_DETECT_MODE_OFF;
}
}
}
/**
* Update the {@code result} camera metadata map with the new value for the
* {@code statistics.faces} and {@code statistics.faceDetectMode}.
*
* <p>Face detect callbacks are processed in the background, and each call to
* {@link #mapResultFaces} will have the latest faces as reflected by the camera1 callbacks.</p>
*
* <p>If the scene mode was set to {@code FACE_PRIORITY} but face detection is disabled,
* the camera will still run face detection in the background, but no faces will be reported
* in the capture result.</p>
*
* @param result a non-{@code null} result
* @param legacyRequest a non-{@code null} request (read-only)
*/
public void mapResultFaces(CameraMetadataNative result, LegacyRequest legacyRequest) {
checkNotNull(result, "result must not be null");
checkNotNull(legacyRequest, "legacyRequest must not be null");
Camera.Face[] faces, previousFaces;
int fdMode;
boolean fdScenePriority;
synchronized (mLock) {
fdMode = mFaceDetectReporting ?
STATISTICS_FACE_DETECT_MODE_SIMPLE : STATISTICS_FACE_DETECT_MODE_OFF;
if (mFaceDetectReporting) {
faces = mFaces;
} else {
faces = null;
}
fdScenePriority = mFaceDetectScenePriority;
previousFaces = mFacesPrev;
mFacesPrev = faces;
}
CameraCharacteristics characteristics = legacyRequest.characteristics;
CaptureRequest request = legacyRequest.captureRequest;
Size previewSize = legacyRequest.previewSize;
Camera.Parameters params = legacyRequest.parameters;
Rect activeArray = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
ZoomData zoomData = ParameterUtils.convertScalerCropRegion(activeArray,
request.get(CaptureRequest.SCALER_CROP_REGION), previewSize, params);
List<Face> convertedFaces = new ArrayList<>();
if (faces != null) {
for (Camera.Face face : faces) {
if (face != null) {
convertedFaces.add(
ParameterUtils.convertFaceFromLegacy(face, activeArray, zoomData));
} else {
Log.w(TAG, "mapResultFaces - read NULL face from camera1 device");
}
}
}
if (DEBUG && previousFaces != faces) { // Log only in verbose and IF the faces changed
Log.v(TAG, "mapResultFaces - changed to " + ListUtils.listToString(convertedFaces));
}
result.set(CaptureResult.STATISTICS_FACES, convertedFaces.toArray(new Face[0]));
result.set(CaptureResult.STATISTICS_FACE_DETECT_MODE, fdMode);
// Override scene mode with FACE_PRIORITY if the request was using FACE_PRIORITY
if (fdScenePriority) {
result.set(CaptureResult.CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_FACE_PRIORITY);
}
}
}