1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.hardware.camera2.legacy;
18
19import android.graphics.Rect;
20import android.hardware.Camera;
21import android.hardware.Camera.FaceDetectionListener;
22import android.hardware.camera2.impl.CameraMetadataNative;
23import android.hardware.camera2.legacy.ParameterUtils.ZoomData;
24import android.hardware.camera2.CameraCharacteristics;
25import android.hardware.camera2.CaptureRequest;
26import android.hardware.camera2.CaptureResult;
27import android.hardware.camera2.params.Face;
28import android.hardware.camera2.utils.ListUtils;
29import android.hardware.camera2.utils.ParamsUtils;
30import android.util.Log;
31import android.util.Size;
32
33import com.android.internal.util.ArrayUtils;
34
35import java.util.ArrayList;
36import java.util.Arrays;
37import java.util.List;
38
39import static android.hardware.camera2.CaptureRequest.*;
40import static com.android.internal.util.Preconditions.*;
41
42/**
43 * Map legacy face detect callbacks into face detection results.
44 */
45@SuppressWarnings("deprecation")
46public class LegacyFaceDetectMapper {
47    private static String TAG = "LegacyFaceDetectMapper";
48    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
49
50    private final Camera mCamera;
51    /** Is the camera capable of face detection? */
52    private final boolean mFaceDetectSupported;
53    /** Is the camera is running face detection? */
54    private boolean mFaceDetectEnabled = false;
55    /** Did the last request say to use SCENE_MODE = FACE_PRIORITY? */
56    private boolean mFaceDetectScenePriority = false;
57    /** Did the last request enable the face detect mode to ON? */
58    private boolean mFaceDetectReporting = false;
59
60    /** Synchronize access to all fields */
61    private final Object mLock = new Object();
62    private Camera.Face[] mFaces;
63    private Camera.Face[] mFacesPrev;
64    /**
65     * Instantiate a new face detect mapper.
66     *
67     * @param camera a non-{@code null} camera1 device
68     * @param characteristics a  non-{@code null} camera characteristics for that camera1
69     *
70     * @throws NullPointerException if any of the args were {@code null}
71     */
72    public LegacyFaceDetectMapper(Camera camera, CameraCharacteristics characteristics) {
73        mCamera = checkNotNull(camera, "camera must not be null");
74        checkNotNull(characteristics, "characteristics must not be null");
75
76        mFaceDetectSupported = ArrayUtils.contains(
77                characteristics.get(
78                        CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES),
79                STATISTICS_FACE_DETECT_MODE_SIMPLE);
80
81        if (!mFaceDetectSupported) {
82            return;
83        }
84
85       mCamera.setFaceDetectionListener(new FaceDetectionListener() {
86
87        @Override
88        public void onFaceDetection(Camera.Face[] faces, Camera camera) {
89            int lengthFaces = faces == null ? 0 : faces.length;
90            synchronized (mLock) {
91                if (mFaceDetectEnabled) {
92                    mFaces = faces;
93                } else if (lengthFaces > 0) {
94                    // stopFaceDetectMode could race against the requests, print a debug log
95                    Log.d(TAG,
96                            "onFaceDetection - Ignored some incoming faces since" +
97                            "face detection was disabled");
98                }
99            }
100
101            if (VERBOSE) {
102                Log.v(TAG, "onFaceDetection - read " + lengthFaces + " faces");
103            }
104        }
105       });
106    }
107
108    /**
109     * Process the face detect mode from the capture request into an api1 face detect toggle.
110     *
111     * <p>This method should be called after the parameters are {@link LegacyRequestMapper mapped}
112     * with the request.</p>
113     *
114     * <p>Callbacks are processed in the background, and the next call to {@link #mapResultTriggers}
115     * will have the latest faces detected as reflected by the camera1 callbacks.</p>
116     *
117     * <p>None of the arguments will be mutated.</p>
118     *
119     * @param captureRequest a non-{@code null} request
120     * @param parameters a non-{@code null} parameters corresponding to this request (read-only)
121     */
122    public void processFaceDetectMode(CaptureRequest captureRequest,
123            Camera.Parameters parameters) {
124        checkNotNull(captureRequest, "captureRequest must not be null");
125
126        /*
127         * statistics.faceDetectMode
128         */
129        int fdMode = ParamsUtils.getOrDefault(captureRequest, STATISTICS_FACE_DETECT_MODE,
130                STATISTICS_FACE_DETECT_MODE_OFF);
131
132        if (fdMode != STATISTICS_FACE_DETECT_MODE_OFF && !mFaceDetectSupported) {
133            Log.w(TAG,
134                    "processFaceDetectMode - Ignoring statistics.faceDetectMode; " +
135                    "face detection is not available");
136            return;
137        }
138
139        /*
140         * control.sceneMode
141         */
142        int sceneMode = ParamsUtils.getOrDefault(captureRequest, CONTROL_SCENE_MODE,
143                CONTROL_SCENE_MODE_DISABLED);
144        if (sceneMode == CONTROL_SCENE_MODE_FACE_PRIORITY && !mFaceDetectSupported) {
145            Log.w(TAG, "processFaceDetectMode - ignoring control.sceneMode == FACE_PRIORITY; " +
146                    "face detection is not available");
147            return;
148        }
149
150        // Print some warnings out in case the values were wrong
151        switch (fdMode) {
152            case STATISTICS_FACE_DETECT_MODE_OFF:
153            case STATISTICS_FACE_DETECT_MODE_SIMPLE:
154                break;
155            case STATISTICS_FACE_DETECT_MODE_FULL:
156                Log.w(TAG,
157                        "processFaceDetectMode - statistics.faceDetectMode == FULL unsupported, " +
158                        "downgrading to SIMPLE");
159                break;
160            default:
161                Log.w(TAG, "processFaceDetectMode - ignoring unknown statistics.faceDetectMode = "
162                        + fdMode);
163                return;
164        }
165
166        boolean enableFaceDetect = (fdMode != STATISTICS_FACE_DETECT_MODE_OFF)
167                || (sceneMode == CONTROL_SCENE_MODE_FACE_PRIORITY);
168        synchronized (mLock) {
169            // Enable/disable face detection if it's changed since last time
170            if (enableFaceDetect != mFaceDetectEnabled) {
171                if (enableFaceDetect) {
172                    mCamera.startFaceDetection();
173
174                    if (VERBOSE) {
175                        Log.v(TAG, "processFaceDetectMode - start face detection");
176                    }
177                } else {
178                    mCamera.stopFaceDetection();
179
180                    if (VERBOSE) {
181                        Log.v(TAG, "processFaceDetectMode - stop face detection");
182                    }
183
184                    mFaces = null;
185                }
186
187                mFaceDetectEnabled = enableFaceDetect;
188                mFaceDetectScenePriority = sceneMode == CONTROL_SCENE_MODE_FACE_PRIORITY;
189                mFaceDetectReporting = fdMode != STATISTICS_FACE_DETECT_MODE_OFF;
190            }
191        }
192    }
193
194    /**
195     * Update the {@code result} camera metadata map with the new value for the
196     * {@code statistics.faces} and {@code statistics.faceDetectMode}.
197     *
198     * <p>Face detect callbacks are processed in the background, and each call to
199     * {@link #mapResultFaces} will have the latest faces as reflected by the camera1 callbacks.</p>
200     *
201     * <p>If the scene mode was set to {@code FACE_PRIORITY} but face detection is disabled,
202     * the camera will still run face detection in the background, but no faces will be reported
203     * in the capture result.</p>
204     *
205     * @param result a non-{@code null} result
206     * @param legacyRequest a non-{@code null} request (read-only)
207     */
208    public void mapResultFaces(CameraMetadataNative result, LegacyRequest legacyRequest) {
209        checkNotNull(result, "result must not be null");
210        checkNotNull(legacyRequest, "legacyRequest must not be null");
211
212        Camera.Face[] faces, previousFaces;
213        int fdMode;
214        boolean fdScenePriority;
215        synchronized (mLock) {
216            fdMode = mFaceDetectReporting ?
217                            STATISTICS_FACE_DETECT_MODE_SIMPLE : STATISTICS_FACE_DETECT_MODE_OFF;
218
219            if (mFaceDetectReporting) {
220                faces = mFaces;
221            } else {
222                faces = null;
223            }
224
225            fdScenePriority = mFaceDetectScenePriority;
226
227            previousFaces = mFacesPrev;
228            mFacesPrev = faces;
229        }
230
231        CameraCharacteristics characteristics = legacyRequest.characteristics;
232        CaptureRequest request = legacyRequest.captureRequest;
233        Size previewSize = legacyRequest.previewSize;
234        Camera.Parameters params = legacyRequest.parameters;
235
236        Rect activeArray = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
237        ZoomData zoomData = ParameterUtils.convertScalerCropRegion(activeArray,
238                request.get(CaptureRequest.SCALER_CROP_REGION), previewSize, params);
239
240        List<Face> convertedFaces = new ArrayList<>();
241        if (faces != null) {
242            for (Camera.Face face : faces) {
243                if (face != null) {
244                    convertedFaces.add(
245                            ParameterUtils.convertFaceFromLegacy(face, activeArray, zoomData));
246                } else {
247                    Log.w(TAG, "mapResultFaces - read NULL face from camera1 device");
248                }
249            }
250        }
251
252        if (VERBOSE && previousFaces != faces) { // Log only in verbose and IF the faces changed
253            Log.v(TAG, "mapResultFaces - changed to " + ListUtils.listToString(convertedFaces));
254        }
255
256        result.set(CaptureResult.STATISTICS_FACES, convertedFaces.toArray(new Face[0]));
257        result.set(CaptureResult.STATISTICS_FACE_DETECT_MODE, fdMode);
258
259        // Override scene mode with FACE_PRIORITY if the request was using FACE_PRIORITY
260        if (fdScenePriority) {
261            result.set(CaptureResult.CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_FACE_PRIORITY);
262        }
263    }
264}
265