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