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