/* * 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.Arrays; 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 VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 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 (VERBOSE) { Log.v(TAG, "onFaceDetection - read " + lengthFaces + " faces"); } } }); } /** * Process the face detect mode from the capture request into an api1 face detect toggle. * *

This method should be called after the parameters are {@link LegacyRequestMapper mapped} * with the request.

* *

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.

* *

None of the arguments will be mutated.

* * @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 (VERBOSE) { Log.v(TAG, "processFaceDetectMode - start face detection"); } } else { mCamera.stopFaceDetection(); if (VERBOSE) { 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}. * *

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.

* *

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.

* * @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 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 (VERBOSE && 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); } } }