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