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