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