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