| /* |
| * 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; |
| private final boolean mFaceDetectSupported; |
| private boolean mFaceDetectEnabled = false; |
| |
| 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. |
| * |
| * <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; |
| } |
| |
| // 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; |
| 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; |
| } |
| } |
| } |
| |
| /** |
| * 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> |
| * |
| * @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; |
| synchronized (mLock) { |
| fdMode = mFaceDetectEnabled ? |
| STATISTICS_FACE_DETECT_MODE_SIMPLE : STATISTICS_FACE_DETECT_MODE_OFF; |
| |
| if (mFaceDetectEnabled) { |
| faces = mFaces; |
| } else { |
| faces = null; |
| } |
| |
| 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 (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); |
| } |
| } |