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