AndroidCamera2Settings.java revision 54c8f898815a233ba6478630940432ddafdb4314
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 com.android.ex.camera2.portability;
18
19import static android.hardware.camera2.CaptureRequest.*;
20
21import android.graphics.Rect;
22import android.hardware.camera2.CameraAccessException;
23import android.hardware.camera2.CameraDevice;
24import android.hardware.camera2.params.MeteringRectangle;
25import android.location.Location;
26import android.util.Range;
27
28import com.android.ex.camera2.portability.CameraCapabilities.FlashMode;
29import com.android.ex.camera2.portability.CameraCapabilities.FocusMode;
30import com.android.ex.camera2.portability.CameraCapabilities.SceneMode;
31import com.android.ex.camera2.portability.CameraCapabilities.WhiteBalance;
32import com.android.ex.camera2.portability.debug.Log;
33import com.android.ex.camera2.utils.Camera2RequestSettingsSet;
34
35import java.util.List;
36import java.util.Objects;
37
38/**
39 * The subclass of {@link CameraSettings} for Android Camera 2 API.
40 */
41public class AndroidCamera2Settings extends CameraSettings {
42    private static final Log.Tag TAG = new Log.Tag("AndCam2Set");
43
44    private final Builder mTemplateSettings;
45    private final Camera2RequestSettingsSet mRequestSettings;
46    private final Rect mActiveArray;
47    private final Rect mCropRectangle;
48
49    /**
50     * Create a settings representation that answers queries of unspecified
51     * options in the same way as the provided template would.
52     *
53     * <p>The default settings provided by the given template are only ever used
54     * for reporting back to the client app (i.e. when it queries an option
55     * it didn't explicitly set first). {@link Camera2RequestSettingsSet}s
56     * generated by an instance of this class will have any settings not
57     * modified using one of that instance's mutators forced to default, so that
58     * their effective values when submitting a capture request will be those of
59     * the template that is provided to the camera framework at that time.</p>
60     *
61     * @param camera Device from which to draw default settings
62     *               (non-{@code null}).
63     * @param template Specific template to use for the defaults.
64     * @param activeArray Boundary coordinates of the sensor's active array
65     *                    (non-{@code null}).
66     * @param preview Dimensions of preview streams.
67     * @param photo Dimensions of captured images.
68     *
69     * @throws IllegalArgumentException If {@code camera} or {@code activeArray}
70     *                                  is {@code null}.
71     * @throws CameraAccessException Upon internal framework/driver failure.
72     */
73    public AndroidCamera2Settings(CameraDevice camera, int template, Rect activeArray,
74                                  Size preview, Size photo) throws CameraAccessException {
75        if (camera == null) {
76            throw new NullPointerException("camera must not be null");
77        }
78        if (activeArray == null) {
79            throw new NullPointerException("activeArray must not be null");
80        }
81
82        mTemplateSettings = camera.createCaptureRequest(template);
83        mRequestSettings = new Camera2RequestSettingsSet();
84        mActiveArray = activeArray;
85        mCropRectangle = new Rect(0, 0, activeArray.width(), activeArray.height());
86
87        Range<Integer> previewFpsRange = mTemplateSettings.get(CONTROL_AE_TARGET_FPS_RANGE);
88        if (previewFpsRange != null) {
89            setPreviewFpsRange(previewFpsRange.getLower(), previewFpsRange.getUpper());
90        }
91        setPreviewSize(preview);
92        // TODO: mCurrentPreviewFormat
93        setPhotoSize(photo);
94        mJpegCompressQuality = queryTemplateDefaultOrMakeOneUp(JPEG_QUALITY, (byte) 0);
95        // TODO: mCurrentPhotoFormat
96        // NB: We're assuming that templates won't be zoomed in by default.
97        mCurrentZoomRatio = CameraCapabilities.ZOOM_RATIO_UNZOOMED;
98        // TODO: mCurrentZoomIndex
99        mExposureCompensationIndex =
100                queryTemplateDefaultOrMakeOneUp(CONTROL_AE_EXPOSURE_COMPENSATION, 0);
101
102        mCurrentFlashMode = flashModeFromRequest();
103        Integer currentFocusMode = mTemplateSettings.get(CONTROL_AF_MODE);
104        if (currentFocusMode != null) {
105            mCurrentFocusMode = AndroidCamera2Capabilities.focusModeFromInt(currentFocusMode);
106        }
107        Integer currentSceneMode = mTemplateSettings.get(CONTROL_SCENE_MODE);
108        if (currentSceneMode != null) {
109            mCurrentSceneMode = AndroidCamera2Capabilities.sceneModeFromInt(currentSceneMode);
110        }
111        Integer whiteBalance = mTemplateSettings.get(CONTROL_AWB_MODE);
112        if (whiteBalance != null) {
113            mWhiteBalance = AndroidCamera2Capabilities.whiteBalanceFromInt(whiteBalance);
114        }
115
116        mVideoStabilizationEnabled = queryTemplateDefaultOrMakeOneUp(
117                        CONTROL_VIDEO_STABILIZATION_MODE, CONTROL_VIDEO_STABILIZATION_MODE_OFF) ==
118                CONTROL_VIDEO_STABILIZATION_MODE_ON;
119        mAutoExposureLocked = queryTemplateDefaultOrMakeOneUp(CONTROL_AE_LOCK, false);
120        mAutoWhiteBalanceLocked = queryTemplateDefaultOrMakeOneUp(CONTROL_AWB_LOCK, false);
121        // TODO: mRecordingHintEnabled
122        // TODO: mGpsData
123        android.util.Size exifThumbnailSize = mTemplateSettings.get(JPEG_THUMBNAIL_SIZE);
124        if (exifThumbnailSize != null) {
125            mExifThumbnailSize =
126                    new Size(exifThumbnailSize.getWidth(), exifThumbnailSize.getHeight());
127        }
128    }
129
130    public AndroidCamera2Settings(AndroidCamera2Settings other) {
131        super(other);
132        mTemplateSettings = other.mTemplateSettings;
133        mRequestSettings = new Camera2RequestSettingsSet(other.mRequestSettings);
134        mActiveArray = other.mActiveArray;
135        mCropRectangle = new Rect(other.mCropRectangle);
136    }
137
138    @Override
139    public CameraSettings copy() {
140        return new AndroidCamera2Settings(this);
141    }
142
143    private <T> T queryTemplateDefaultOrMakeOneUp(Key<T> key, T defaultDefault) {
144        T val = mTemplateSettings.get(key);
145        if (val != null) {
146            return val;
147        } else {
148            // Spoof the default so matchesTemplateDefault excludes this key from generated sets.
149            // This approach beats a simple sentinel because it provides basic boolean support.
150            mTemplateSettings.set(key, defaultDefault);
151            return defaultDefault;
152        }
153    }
154
155    private FlashMode flashModeFromRequest() {
156        Integer autoExposure = mTemplateSettings.get(CONTROL_AE_MODE);
157        if (autoExposure != null) {
158            switch (autoExposure) {
159                case CONTROL_AE_MODE_ON:
160                    return FlashMode.OFF;
161                case CONTROL_AE_MODE_ON_AUTO_FLASH:
162                    return FlashMode.AUTO;
163                case CONTROL_AE_MODE_ON_ALWAYS_FLASH: {
164                    if (mTemplateSettings.get(FLASH_MODE) == FLASH_MODE_TORCH) {
165                        return FlashMode.TORCH;
166                    } else {
167                        return FlashMode.ON;
168                    }
169                }
170                case CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE:
171                    return FlashMode.RED_EYE;
172            }
173        }
174        return null;
175    }
176
177    @Override
178    public void setZoomRatio(float ratio) {
179        super.setZoomRatio(ratio);
180        mCropRectangle.set(0, 0,
181                toIntConstrained(
182                        mActiveArray.width() / mCurrentZoomRatio, 0, mActiveArray.width()),
183                toIntConstrained(
184                        mActiveArray.height() / mCurrentZoomRatio, 0, mActiveArray.height()));
185        mCropRectangle.offsetTo((mActiveArray.width() - mCropRectangle.width()) / 2,
186                (mActiveArray.height() - mCropRectangle.height()) / 2);
187    }
188
189    private boolean matchesTemplateDefault(Key<?> setting) {
190        if (setting == CONTROL_AE_REGIONS) {
191            return mMeteringAreas.size() == 0;
192        } else if (setting == CONTROL_AF_REGIONS) {
193            return mFocusAreas.size() == 0;
194        } else if (setting == CONTROL_AE_TARGET_FPS_RANGE) {
195            Range defaultFpsRange = mTemplateSettings.get(CONTROL_AE_TARGET_FPS_RANGE);
196            return (mPreviewFpsRangeMin == 0 && mPreviewFpsRangeMax == 0) ||
197                    (defaultFpsRange != null && mPreviewFpsRangeMin == defaultFpsRange.getLower() &&
198                            mPreviewFpsRangeMax == defaultFpsRange.getUpper());
199        } else if (setting == JPEG_QUALITY) {
200            return Objects.equals(mJpegCompressQuality,
201                    mTemplateSettings.get(JPEG_QUALITY));
202        } else if (setting == CONTROL_AE_EXPOSURE_COMPENSATION) {
203            return Objects.equals(mExposureCompensationIndex,
204                    mTemplateSettings.get(CONTROL_AE_EXPOSURE_COMPENSATION));
205        } else if (setting == CONTROL_VIDEO_STABILIZATION_MODE) {
206            Integer videoStabilization = mTemplateSettings.get(CONTROL_VIDEO_STABILIZATION_MODE);
207            return (videoStabilization != null &&
208                    (mVideoStabilizationEnabled && videoStabilization ==
209                            CONTROL_VIDEO_STABILIZATION_MODE_ON) ||
210                    (!mVideoStabilizationEnabled && videoStabilization ==
211                            CONTROL_VIDEO_STABILIZATION_MODE_OFF));
212        } else if (setting == CONTROL_AE_LOCK) {
213            return Objects.equals(mAutoExposureLocked, mTemplateSettings.get(CONTROL_AE_LOCK));
214        } else if (setting == CONTROL_AWB_LOCK) {
215            return Objects.equals(mAutoWhiteBalanceLocked, mTemplateSettings.get(CONTROL_AWB_LOCK));
216        } else if (setting == JPEG_THUMBNAIL_SIZE) {
217            android.util.Size defaultThumbnailSize = mTemplateSettings.get(JPEG_THUMBNAIL_SIZE);
218            return (mExifThumbnailSize.width() == 0 && mExifThumbnailSize.height() == 0) ||
219                    (defaultThumbnailSize != null &&
220                            mExifThumbnailSize.width() == defaultThumbnailSize.getWidth() &&
221                            mExifThumbnailSize.height() == defaultThumbnailSize.getHeight());
222        }
223        Log.w(TAG, "Settings implementation checked default of unhandled option key");
224        // Since this class isn't equipped to handle it, claim it matches the default to prevent
225        // updateRequestSettingOrForceToDefault from going with the user-provided preference
226        return true;
227    }
228
229    private <T> void updateRequestSettingOrForceToDefault(Key<T> setting, T possibleChoice) {
230        mRequestSettings.set(setting, matchesTemplateDefault(setting) ? null : possibleChoice);
231    }
232
233    public Camera2RequestSettingsSet getRequestSettings() {
234        updateRequestSettingOrForceToDefault(CONTROL_AE_REGIONS,
235                legacyAreasToMeteringRectangles(mMeteringAreas));
236        updateRequestSettingOrForceToDefault(CONTROL_AF_REGIONS,
237                legacyAreasToMeteringRectangles(mFocusAreas));
238        updateRequestSettingOrForceToDefault(CONTROL_AE_TARGET_FPS_RANGE,
239                new Range(mPreviewFpsRangeMin, mPreviewFpsRangeMax));
240        // TODO: mCurrentPreviewFormat
241        updateRequestSettingOrForceToDefault(JPEG_QUALITY, mJpegCompressQuality);
242        // TODO: mCurrentPhotoFormat
243        mRequestSettings.set(SCALER_CROP_REGION, mCropRectangle);
244        // TODO: mCurrentZoomIndex
245        updateRequestSettingOrForceToDefault(CONTROL_AE_EXPOSURE_COMPENSATION,
246                mExposureCompensationIndex);
247        updateRequestFlashMode();
248        updateRequestFocusMode();
249        updateRequestSceneMode();
250        updateRequestWhiteBalance();
251        updateRequestSettingOrForceToDefault(CONTROL_VIDEO_STABILIZATION_MODE,
252                mVideoStabilizationEnabled ?
253                        CONTROL_VIDEO_STABILIZATION_MODE_ON : CONTROL_VIDEO_STABILIZATION_MODE_OFF);
254        // OIS shouldn't be on if software video stabilization is.
255        mRequestSettings.set(LENS_OPTICAL_STABILIZATION_MODE,
256                mVideoStabilizationEnabled ? LENS_OPTICAL_STABILIZATION_MODE_OFF :
257                        null);
258        updateRequestSettingOrForceToDefault(CONTROL_AE_LOCK, mAutoExposureLocked);
259        updateRequestSettingOrForceToDefault(CONTROL_AWB_LOCK, mAutoWhiteBalanceLocked);
260        // TODO: mRecordingHintEnabled
261        updateRequestGpsData();
262        updateRequestSettingOrForceToDefault(JPEG_THUMBNAIL_SIZE,
263                new android.util.Size(
264                        mExifThumbnailSize.width(), mExifThumbnailSize.height()));
265
266        return mRequestSettings;
267    }
268
269    private MeteringRectangle[] legacyAreasToMeteringRectangles(
270            List<android.hardware.Camera.Area> reference) {
271        MeteringRectangle[] transformed = null;
272        if (reference.size() > 0) {
273            transformed = new MeteringRectangle[reference.size()];
274            for (int index = 0; index < reference.size(); ++index) {
275                android.hardware.Camera.Area source = reference.get(index);
276                Rect rectangle = source.rect;
277
278                // Old API coordinates were [-1000,1000]; new ones are [0,ACTIVE_ARRAY_SIZE).
279                // We're also going from preview image--relative to sensor active array--relative.
280                double oldLeft = (rectangle.left + 1000) / 2000.0;
281                double oldTop = (rectangle.top + 1000) / 2000.0;
282                double oldRight = (rectangle.right + 1000) / 2000.0;
283                double oldBottom = (rectangle.bottom + 1000) / 2000.0;
284                int left = mCropRectangle.left + toIntConstrained(
285                        mCropRectangle.width() * oldLeft, 0, mCropRectangle.width() - 1);
286                int top = mCropRectangle.top + toIntConstrained(
287                        mCropRectangle.height() * oldTop, 0, mCropRectangle.height() - 1);
288                int right = mCropRectangle.left + toIntConstrained(
289                        mCropRectangle.width() * oldRight, 0, mCropRectangle.width() - 1);
290                int bottom = mCropRectangle.top + toIntConstrained(
291                        mCropRectangle.height() * oldBottom, 0, mCropRectangle.height() - 1);
292                transformed[index] = new MeteringRectangle(left, top, right - left, bottom - top,
293                        source.weight);
294            }
295        }
296        return transformed;
297    }
298
299    private int toIntConstrained(double original, int min, int max) {
300        original = Math.max(original, min);
301        original = Math.min(original, max);
302        return (int) original;
303    }
304
305    private void updateRequestFlashMode() {
306        Integer aeMode = null;
307        Integer flashMode = null;
308        if (mCurrentFlashMode != null) {
309            switch (mCurrentFlashMode) {
310                case AUTO: {
311                    aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH;
312                    break;
313                }
314                case OFF: {
315                    aeMode = CONTROL_AE_MODE_ON;
316                    flashMode = FLASH_MODE_OFF;
317                    break;
318                }
319                case ON: {
320                    aeMode = CONTROL_AE_MODE_ON_ALWAYS_FLASH;
321                    flashMode = FLASH_MODE_SINGLE;
322                    break;
323                }
324                case TORCH: {
325                    flashMode = FLASH_MODE_TORCH;
326                    break;
327                }
328                case RED_EYE: {
329                    aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE;
330                    break;
331                }
332                default: {
333                    Log.w(TAG, "Unable to convert to API 2 flash mode: " + mCurrentFlashMode);
334                    break;
335                }
336            }
337        }
338        mRequestSettings.set(CONTROL_AE_MODE, aeMode);
339        mRequestSettings.set(FLASH_MODE, flashMode);
340    }
341
342    private void updateRequestFocusMode() {
343        Integer mode = null;
344        if (mCurrentFocusMode != null) {
345            switch (mCurrentFocusMode) {
346                case AUTO: {
347                    mode = CONTROL_AF_MODE_AUTO;
348                    break;
349                }
350                case CONTINUOUS_PICTURE: {
351                    mode = CONTROL_AF_MODE_CONTINUOUS_PICTURE;
352                    break;
353                }
354                case CONTINUOUS_VIDEO: {
355                    mode = CONTROL_AF_MODE_CONTINUOUS_VIDEO;
356                    break;
357                }
358                case EXTENDED_DOF: {
359                    mode = CONTROL_AF_MODE_EDOF;
360                    break;
361                }
362                case FIXED: {
363                    mode = CONTROL_AF_MODE_OFF;
364                    break;
365                }
366                // TODO: We cannot support INFINITY
367                case MACRO: {
368                    mode = CONTROL_AF_MODE_MACRO;
369                    break;
370                }
371                default: {
372                    Log.w(TAG, "Unable to convert to API 2 focus mode: " + mCurrentFocusMode);
373                    break;
374                }
375            }
376        }
377        mRequestSettings.set(CONTROL_AF_MODE, mode);
378    }
379
380    private void updateRequestSceneMode() {
381        Integer mode = null;
382        if (mCurrentSceneMode != null) {
383            switch (mCurrentSceneMode) {
384                case AUTO: {
385                    mode = CONTROL_SCENE_MODE_DISABLED;
386                    break;
387                }
388                case ACTION: {
389                    mode = CONTROL_SCENE_MODE_ACTION;
390                    break;
391                }
392                case BARCODE: {
393                    mode = CONTROL_SCENE_MODE_BARCODE;
394                    break;
395                }
396                case BEACH: {
397                    mode = CONTROL_SCENE_MODE_BEACH;
398                    break;
399                }
400                case CANDLELIGHT: {
401                    mode = CONTROL_SCENE_MODE_CANDLELIGHT;
402                    break;
403                }
404                case FIREWORKS: {
405                    mode = CONTROL_SCENE_MODE_FIREWORKS;
406                    break;
407                }
408                case HDR: {
409                    mode = LegacyVendorTags.CONTROL_SCENE_MODE_HDR;
410                    break;
411                }
412                case LANDSCAPE: {
413                    mode = CONTROL_SCENE_MODE_LANDSCAPE;
414                    break;
415                }
416                case NIGHT: {
417                    mode = CONTROL_SCENE_MODE_NIGHT;
418                    break;
419                }
420                // TODO: We cannot support NIGHT_PORTRAIT
421                case PARTY: {
422                    mode = CONTROL_SCENE_MODE_PARTY;
423                    break;
424                }
425                case PORTRAIT: {
426                    mode = CONTROL_SCENE_MODE_PORTRAIT;
427                    break;
428                }
429                case SNOW: {
430                    mode = CONTROL_SCENE_MODE_SNOW;
431                    break;
432                }
433                case SPORTS: {
434                    mode = CONTROL_SCENE_MODE_SPORTS;
435                    break;
436                }
437                case STEADYPHOTO: {
438                    mode = CONTROL_SCENE_MODE_STEADYPHOTO;
439                    break;
440                }
441                case SUNSET: {
442                    mode = CONTROL_SCENE_MODE_SUNSET;
443                    break;
444                }
445                case THEATRE: {
446                    mode = CONTROL_SCENE_MODE_THEATRE;
447                    break;
448                }
449                default: {
450                    Log.w(TAG, "Unable to convert to API 2 scene mode: " + mCurrentSceneMode);
451                    break;
452                }
453            }
454        }
455        mRequestSettings.set(CONTROL_SCENE_MODE, mode);
456    }
457
458    private void updateRequestWhiteBalance() {
459        Integer mode = null;
460        if (mWhiteBalance != null) {
461            switch (mWhiteBalance) {
462                case AUTO: {
463                    mode = CONTROL_AWB_MODE_AUTO;
464                    break;
465                }
466                case CLOUDY_DAYLIGHT: {
467                    mode = CONTROL_AWB_MODE_CLOUDY_DAYLIGHT;
468                    break;
469                }
470                case DAYLIGHT: {
471                    mode = CONTROL_AWB_MODE_DAYLIGHT;
472                    break;
473                }
474                case FLUORESCENT: {
475                    mode = CONTROL_AWB_MODE_FLUORESCENT;
476                    break;
477                }
478                case INCANDESCENT: {
479                    mode = CONTROL_AWB_MODE_INCANDESCENT;
480                    break;
481                }
482                case SHADE: {
483                    mode = CONTROL_AWB_MODE_SHADE;
484                    break;
485                }
486                case TWILIGHT: {
487                    mode = CONTROL_AWB_MODE_TWILIGHT;
488                    break;
489                }
490                case WARM_FLUORESCENT: {
491                    mode = CONTROL_AWB_MODE_WARM_FLUORESCENT;
492                    break;
493                }
494                default: {
495                    Log.w(TAG, "Unable to convert to API 2 white balance: " + mWhiteBalance);
496                    break;
497                }
498            }
499        }
500        mRequestSettings.set(CONTROL_AWB_MODE, mode);
501    }
502
503    private void updateRequestGpsData() {
504        if (mGpsData == null || mGpsData.processingMethod == null) {
505            // It's a hack since we always use GPS time stamp but does
506            // not use other fields sometimes. Setting processing
507            // method to null means the other fields should not be used.
508            mRequestSettings.set(JPEG_GPS_LOCATION, null);
509        } else {
510            Location location = new Location(mGpsData.processingMethod);
511            location.setTime(mGpsData.timeStamp);
512            location.setAltitude(mGpsData.altitude);
513            location.setLatitude(mGpsData.latitude);
514            location.setLongitude(mGpsData.longitude);
515            mRequestSettings.set(JPEG_GPS_LOCATION, location);
516        }
517    }
518}
519