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.Parameters;
22import android.hardware.camera2.CameraCharacteristics;
23import android.hardware.camera2.CaptureRequest;
24import android.hardware.camera2.CaptureResult;
25import android.hardware.camera2.impl.CameraMetadataNative;
26import android.hardware.camera2.legacy.ParameterUtils.WeightedRectangle;
27import android.hardware.camera2.legacy.ParameterUtils.ZoomData;
28import android.hardware.camera2.params.MeteringRectangle;
29import android.hardware.camera2.utils.ListUtils;
30import android.hardware.camera2.utils.ParamsUtils;
31import android.util.Log;
32import android.util.Size;
33
34import java.util.ArrayList;
35import java.util.List;
36
37import static android.hardware.camera2.CaptureResult.*;
38
39/**
40 * Provide legacy-specific implementations of camera2 CaptureResult for legacy devices.
41 */
42@SuppressWarnings("deprecation")
43public class LegacyResultMapper {
44    private static final String TAG = "LegacyResultMapper";
45    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
46
47    private LegacyRequest mCachedRequest = null;
48    private CameraMetadataNative mCachedResult = null;
49
50    /**
51     * Generate capture result metadata from the legacy camera request.
52     *
53     * <p>This method caches and reuses the result from the previous call to this method if
54     * the {@code parameters} of the subsequent {@link LegacyRequest} passed to this method
55     * have not changed.</p>
56     *
57     * @param legacyRequest a non-{@code null} legacy request containing the latest parameters
58     * @param timestamp the timestamp to use for this result in nanoseconds.
59     *
60     * @return {@link CameraMetadataNative} object containing result metadata.
61     */
62    public CameraMetadataNative cachedConvertResultMetadata(
63            LegacyRequest legacyRequest, long timestamp) {
64        CameraMetadataNative result;
65        boolean cached;
66
67        /*
68         * Attempt to look up the result from the cache if the parameters haven't changed
69         */
70        if (mCachedRequest != null && legacyRequest.parameters.same(mCachedRequest.parameters)) {
71            result = new CameraMetadataNative(mCachedResult);
72            cached = true;
73        } else {
74            result = convertResultMetadata(legacyRequest);
75            cached = false;
76
77            // Always cache a *copy* of the metadata result,
78            // since api2's client side takes ownership of it after it receives a result
79            mCachedRequest = legacyRequest;
80            mCachedResult = new CameraMetadataNative(result);
81        }
82
83        /*
84         * Unconditionally set fields that change in every single frame
85         */
86        {
87            // sensor.timestamp
88            result.set(SENSOR_TIMESTAMP, timestamp);
89        }
90
91        if (VERBOSE) {
92            Log.v(TAG, "cachedConvertResultMetadata - cached? " + cached +
93                    " timestamp = " + timestamp);
94
95            Log.v(TAG, "----- beginning of result dump ------");
96            result.dumpToLog();
97            Log.v(TAG, "----- end of result dump ------");
98        }
99
100        return result;
101    }
102
103    /**
104     * Generate capture result metadata from the legacy camera request.
105     *
106     * @param legacyRequest a non-{@code null} legacy request containing the latest parameters
107     * @return a {@link CameraMetadataNative} object containing result metadata.
108     */
109    private static CameraMetadataNative convertResultMetadata(LegacyRequest legacyRequest) {
110        CameraCharacteristics characteristics = legacyRequest.characteristics;
111        CaptureRequest request = legacyRequest.captureRequest;
112        Size previewSize = legacyRequest.previewSize;
113        Camera.Parameters params = legacyRequest.parameters;
114
115        CameraMetadataNative result = new CameraMetadataNative();
116
117        Rect activeArraySize = characteristics.get(
118                CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
119        ZoomData zoomData = ParameterUtils.convertScalerCropRegion(activeArraySize,
120                request.get(CaptureRequest.SCALER_CROP_REGION), previewSize, params);
121
122        /*
123         * colorCorrection
124         */
125        // colorCorrection.aberrationMode
126        {
127            // Always hardcoded to FAST
128            result.set(COLOR_CORRECTION_ABERRATION_MODE, COLOR_CORRECTION_ABERRATION_MODE_FAST);
129        }
130
131        /*
132         * control
133         */
134
135        /*
136         * control.ae*
137         */
138        mapAe(result, characteristics, request, activeArraySize, zoomData, /*out*/params);
139
140        /*
141         * control.af*
142         */
143        mapAf(result, activeArraySize, zoomData, /*out*/params);
144
145        /*
146         * control.awb*
147         */
148        mapAwb(result, /*out*/params);
149
150        /*
151         * control.captureIntent
152         */
153        {
154            int captureIntent = ParamsUtils.getOrDefault(request,
155                    CaptureRequest.CONTROL_CAPTURE_INTENT,
156                    /*defaultValue*/CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW);
157
158            captureIntent = LegacyRequestMapper.filterSupportedCaptureIntent(captureIntent);
159
160            result.set(CONTROL_CAPTURE_INTENT, captureIntent);
161        }
162
163        /*
164         * control.mode
165         */
166        {
167            int controlMode = ParamsUtils.getOrDefault(request, CaptureRequest.CONTROL_MODE,
168                    CONTROL_MODE_AUTO);
169            if (controlMode == CaptureResult.CONTROL_MODE_USE_SCENE_MODE) {
170                result.set(CONTROL_MODE, CONTROL_MODE_USE_SCENE_MODE);
171            } else {
172                result.set(CONTROL_MODE, CONTROL_MODE_AUTO);
173            }
174        }
175
176        /*
177         * control.sceneMode
178         */
179        {
180            String legacySceneMode = params.getSceneMode();
181            int mode = LegacyMetadataMapper.convertSceneModeFromLegacy(legacySceneMode);
182            if (mode != LegacyMetadataMapper.UNKNOWN_MODE) {
183                result.set(CaptureResult.CONTROL_SCENE_MODE, mode);
184                // In case of SCENE_MODE == FACE_PRIORITY, LegacyFaceDetectMapper will override
185                // the result to say SCENE_MODE == FACE_PRIORITY.
186            }  else {
187                Log.w(TAG, "Unknown scene mode " + legacySceneMode +
188                        " returned by camera HAL, setting to disabled.");
189                result.set(CaptureResult.CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_DISABLED);
190            }
191        }
192
193        /*
194         * control.effectMode
195         */
196        {
197            String legacyEffectMode = params.getColorEffect();
198            int mode = LegacyMetadataMapper.convertEffectModeFromLegacy(legacyEffectMode);
199            if (mode != LegacyMetadataMapper.UNKNOWN_MODE) {
200                result.set(CaptureResult.CONTROL_EFFECT_MODE, mode);
201            } else {
202                Log.w(TAG, "Unknown effect mode " + legacyEffectMode +
203                        " returned by camera HAL, setting to off.");
204                result.set(CaptureResult.CONTROL_EFFECT_MODE, CONTROL_EFFECT_MODE_OFF);
205            }
206        }
207
208        // control.videoStabilizationMode
209        {
210            int stabMode =
211                    (params.isVideoStabilizationSupported() && params.getVideoStabilization()) ?
212                        CONTROL_VIDEO_STABILIZATION_MODE_ON :
213                        CONTROL_VIDEO_STABILIZATION_MODE_OFF;
214            result.set(CONTROL_VIDEO_STABILIZATION_MODE, stabMode);
215        }
216
217        /*
218         * flash
219         */
220        {
221            // flash.mode, flash.state mapped in mapAeAndFlashMode
222        }
223
224        /*
225         * lens
226         */
227        // lens.focusDistance
228        {
229            if (Parameters.FOCUS_MODE_INFINITY.equals(params.getFocusMode())) {
230                result.set(CaptureResult.LENS_FOCUS_DISTANCE, 0.0f);
231            }
232        }
233
234        // lens.focalLength
235        result.set(CaptureResult.LENS_FOCAL_LENGTH, params.getFocalLength());
236
237        /*
238         * request
239         */
240        // request.pipelineDepth
241        result.set(REQUEST_PIPELINE_DEPTH,
242                characteristics.get(CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH));
243
244        /*
245         * scaler
246         */
247        mapScaler(result, zoomData, /*out*/params);
248
249        /*
250         * sensor
251         */
252        // sensor.timestamp varies every frame; mapping is done in #cachedConvertResultMetadata
253        {
254            // Unconditionally no test patterns
255            result.set(SENSOR_TEST_PATTERN_MODE, SENSOR_TEST_PATTERN_MODE_OFF);
256        }
257
258        /*
259         * jpeg
260         */
261        // jpeg.gpsLocation
262        result.set(JPEG_GPS_LOCATION, request.get(CaptureRequest.JPEG_GPS_LOCATION));
263
264        // jpeg.orientation
265        result.set(JPEG_ORIENTATION, request.get(CaptureRequest.JPEG_ORIENTATION));
266
267        // jpeg.quality
268        result.set(JPEG_QUALITY, (byte) params.getJpegQuality());
269
270        // jpeg.thumbnailQuality
271        result.set(JPEG_THUMBNAIL_QUALITY, (byte) params.getJpegThumbnailQuality());
272
273        // jpeg.thumbnailSize
274        Camera.Size s = params.getJpegThumbnailSize();
275        if (s != null) {
276            result.set(JPEG_THUMBNAIL_SIZE, ParameterUtils.convertSize(s));
277        } else {
278            Log.w(TAG, "Null thumbnail size received from parameters.");
279        }
280
281        /*
282         * noiseReduction.*
283         */
284        // noiseReduction.mode
285        result.set(NOISE_REDUCTION_MODE, NOISE_REDUCTION_MODE_FAST);
286
287        return result;
288    }
289
290    private static void mapAe(CameraMetadataNative m,
291            CameraCharacteristics characteristics,
292            CaptureRequest request, Rect activeArray, ZoomData zoomData, /*out*/Parameters p) {
293        // control.aeAntiBandingMode
294        {
295            int antiBandingMode = LegacyMetadataMapper.convertAntiBandingModeOrDefault(
296                    p.getAntibanding());
297            m.set(CONTROL_AE_ANTIBANDING_MODE, antiBandingMode);
298        }
299
300        // control.aeExposureCompensation
301        {
302            m.set(CONTROL_AE_EXPOSURE_COMPENSATION, p.getExposureCompensation());
303        }
304
305        // control.aeLock
306        {
307            boolean lock = p.isAutoExposureLockSupported() ? p.getAutoExposureLock() : false;
308            m.set(CONTROL_AE_LOCK, lock);
309            if (VERBOSE) {
310                Log.v(TAG,
311                        "mapAe - android.control.aeLock = " + lock +
312                        ", supported = " + p.isAutoExposureLockSupported());
313            }
314
315            Boolean requestLock = request.get(CaptureRequest.CONTROL_AE_LOCK);
316            if (requestLock != null && requestLock != lock) {
317                Log.w(TAG,
318                        "mapAe - android.control.aeLock was requested to " + requestLock +
319                        " but resulted in " + lock);
320            }
321        }
322
323        // control.aeMode, flash.mode, flash.state
324        mapAeAndFlashMode(m, characteristics, p);
325
326        // control.aeState
327        if (LegacyMetadataMapper.LIE_ABOUT_AE_STATE) {
328            // Lie to pass CTS temporarily.
329            // TODO: Implement precapture trigger, after which we can report CONVERGED ourselves
330            m.set(CONTROL_AE_STATE, CONTROL_AE_STATE_CONVERGED);
331        }
332
333        // control.aeRegions
334        if (p.getMaxNumMeteringAreas() > 0) {
335            if (VERBOSE) {
336                String meteringAreas = p.get("metering-areas");
337                Log.v(TAG, "mapAe - parameter dump; metering-areas: " + meteringAreas);
338            }
339
340            MeteringRectangle[] meteringRectArray = getMeteringRectangles(activeArray,
341                    zoomData, p.getMeteringAreas(), "AE");
342
343            m.set(CONTROL_AE_REGIONS, meteringRectArray);
344        }
345
346    }
347
348    private static void mapAf(CameraMetadataNative m,
349            Rect activeArray, ZoomData zoomData, Camera.Parameters p) {
350        // control.afMode
351        m.set(CaptureResult.CONTROL_AF_MODE, convertLegacyAfMode(p.getFocusMode()));
352
353        // control.afRegions
354        if (p.getMaxNumFocusAreas() > 0) {
355            if (VERBOSE) {
356                String focusAreas = p.get("focus-areas");
357                Log.v(TAG, "mapAe - parameter dump; focus-areas: " + focusAreas);
358            }
359
360            MeteringRectangle[] meteringRectArray = getMeteringRectangles(activeArray,
361                    zoomData, p.getFocusAreas(), "AF");
362
363            m.set(CONTROL_AF_REGIONS, meteringRectArray);
364        }
365    }
366
367    private static void mapAwb(CameraMetadataNative m, Camera.Parameters p) {
368        // control.awbLock
369        {
370            boolean lock = p.isAutoWhiteBalanceLockSupported() ?
371                    p.getAutoWhiteBalanceLock() : false;
372            m.set(CONTROL_AWB_LOCK, lock);
373        }
374
375        // control.awbMode
376        {
377            int awbMode = convertLegacyAwbMode(p.getWhiteBalance());
378            m.set(CONTROL_AWB_MODE, awbMode);
379        }
380    }
381
382    private static MeteringRectangle[] getMeteringRectangles(Rect activeArray, ZoomData zoomData,
383            List<Camera.Area> meteringAreaList, String regionName) {
384        List<MeteringRectangle> meteringRectList = new ArrayList<>();
385        if (meteringAreaList != null) {
386            for (Camera.Area area : meteringAreaList) {
387                WeightedRectangle rect =
388                        ParameterUtils.convertCameraAreaToActiveArrayRectangle(
389                                activeArray, zoomData, area);
390
391                meteringRectList.add(rect.toMetering());
392            }
393        }
394
395        if (VERBOSE) {
396            Log.v(TAG,
397                    "Metering rectangles for " + regionName + ": "
398                     + ListUtils.listToString(meteringRectList));
399        }
400
401        return meteringRectList.toArray(new MeteringRectangle[0]);
402    }
403
404    /** Map results for control.aeMode, flash.mode, flash.state */
405    private static void mapAeAndFlashMode(CameraMetadataNative m,
406            CameraCharacteristics characteristics, Parameters p) {
407        // Default: AE mode on but flash never fires
408        int flashMode = FLASH_MODE_OFF;
409        // If there is no flash on this camera, the state is always unavailable
410        // , otherwise it's only known for TORCH/SINGLE modes
411        Integer flashState = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE)
412                ? null : FLASH_STATE_UNAVAILABLE;
413        int aeMode = CONTROL_AE_MODE_ON;
414
415        String flashModeSetting = p.getFlashMode();
416
417        if (flashModeSetting != null) {
418            switch (flashModeSetting) {
419                case Parameters.FLASH_MODE_OFF:
420                    break; // ok, using default
421                case Parameters.FLASH_MODE_AUTO:
422                    aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH;
423                    break;
424                case Parameters.FLASH_MODE_ON:
425                    // flashMode = SINGLE + aeMode = ON is indistinguishable from ON_ALWAYS_FLASH
426                    flashMode = FLASH_MODE_SINGLE;
427                    aeMode = CONTROL_AE_MODE_ON_ALWAYS_FLASH;
428                    flashState = FLASH_STATE_FIRED;
429                    break;
430                case Parameters.FLASH_MODE_RED_EYE:
431                    aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE;
432                    break;
433                case Parameters.FLASH_MODE_TORCH:
434                    flashMode = FLASH_MODE_TORCH;
435                    flashState = FLASH_STATE_FIRED;
436                    break;
437                default:
438                    Log.w(TAG,
439                            "mapAeAndFlashMode - Ignoring unknown flash mode " + p.getFlashMode());
440            }
441        }
442
443        // flash.state
444        m.set(FLASH_STATE, flashState);
445        // flash.mode
446        m.set(FLASH_MODE, flashMode);
447        // control.aeMode
448        m.set(CONTROL_AE_MODE, aeMode);
449    }
450
451    private static int convertLegacyAfMode(String mode) {
452        if (mode == null) {
453            Log.w(TAG, "convertLegacyAfMode - no AF mode, default to OFF");
454            return CONTROL_AF_MODE_OFF;
455        }
456
457        switch (mode) {
458            case Parameters.FOCUS_MODE_AUTO:
459                return CONTROL_AF_MODE_AUTO;
460            case Parameters.FOCUS_MODE_CONTINUOUS_PICTURE:
461                return CONTROL_AF_MODE_CONTINUOUS_PICTURE;
462            case Parameters.FOCUS_MODE_CONTINUOUS_VIDEO:
463                return CONTROL_AF_MODE_CONTINUOUS_VIDEO;
464            case Parameters.FOCUS_MODE_EDOF:
465                return CONTROL_AF_MODE_EDOF;
466            case Parameters.FOCUS_MODE_MACRO:
467                return CONTROL_AF_MODE_MACRO;
468            case Parameters.FOCUS_MODE_FIXED:
469                return CONTROL_AF_MODE_OFF;
470            case Parameters.FOCUS_MODE_INFINITY:
471                return CONTROL_AF_MODE_OFF;
472            default:
473                Log.w(TAG, "convertLegacyAfMode - unknown mode " + mode + " , ignoring");
474                return CONTROL_AF_MODE_OFF;
475        }
476    }
477
478    private static int convertLegacyAwbMode(String mode) {
479        if (mode == null) {
480            // OK: camera1 api may not support changing WB modes; assume AUTO
481            return CONTROL_AWB_MODE_AUTO;
482        }
483
484        switch (mode) {
485            case Camera.Parameters.WHITE_BALANCE_AUTO:
486                return CONTROL_AWB_MODE_AUTO;
487            case Camera.Parameters.WHITE_BALANCE_INCANDESCENT:
488                return CONTROL_AWB_MODE_INCANDESCENT;
489            case Camera.Parameters.WHITE_BALANCE_FLUORESCENT:
490                return CONTROL_AWB_MODE_FLUORESCENT;
491            case Camera.Parameters.WHITE_BALANCE_WARM_FLUORESCENT:
492                return CONTROL_AWB_MODE_WARM_FLUORESCENT;
493            case Camera.Parameters.WHITE_BALANCE_DAYLIGHT:
494                return CONTROL_AWB_MODE_DAYLIGHT;
495            case Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT:
496                return CONTROL_AWB_MODE_CLOUDY_DAYLIGHT;
497            case Camera.Parameters.WHITE_BALANCE_TWILIGHT:
498                return CONTROL_AWB_MODE_TWILIGHT;
499            case Camera.Parameters.WHITE_BALANCE_SHADE:
500                return CONTROL_AWB_MODE_SHADE;
501            default:
502                Log.w(TAG, "convertAwbMode - unrecognized WB mode " + mode);
503                return CONTROL_AWB_MODE_AUTO;
504        }
505    }
506
507    /** Map results for scaler.* */
508    private static void mapScaler(CameraMetadataNative m,
509            ZoomData zoomData,
510            /*out*/Parameters p) {
511        /*
512         * scaler.cropRegion
513         */
514        {
515            m.set(SCALER_CROP_REGION, zoomData.reportedCrop);
516        }
517    }
518}
519