1 /* 2 * Copyright (C) 2015 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 17 18 package android.hardware.camera2.params; 19 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.SystemApi; 23 import android.graphics.ImageFormat; 24 import android.hardware.camera2.CameraCaptureSession; 25 import android.hardware.camera2.CameraDevice; 26 import android.hardware.camera2.utils.HashCodeHelpers; 27 import android.hardware.camera2.utils.SurfaceUtils; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 import android.util.Log; 31 import android.util.Size; 32 import android.view.Surface; 33 34 import static com.android.internal.util.Preconditions.*; 35 36 /** 37 * A class for describing camera output, which contains a {@link Surface} and its specific 38 * configuration for creating capture session. 39 * 40 * @see CameraDevice#createCaptureSessionByOutputConfiguration 41 * 42 */ 43 public final class OutputConfiguration implements Parcelable { 44 45 /** 46 * Rotation constant: 0 degree rotation (no rotation) 47 * 48 * @hide 49 */ 50 @SystemApi 51 public static final int ROTATION_0 = 0; 52 53 /** 54 * Rotation constant: 90 degree counterclockwise rotation. 55 * 56 * @hide 57 */ 58 @SystemApi 59 public static final int ROTATION_90 = 1; 60 61 /** 62 * Rotation constant: 180 degree counterclockwise rotation. 63 * 64 * @hide 65 */ 66 @SystemApi 67 public static final int ROTATION_180 = 2; 68 69 /** 70 * Rotation constant: 270 degree counterclockwise rotation. 71 * 72 * @hide 73 */ 74 @SystemApi 75 public static final int ROTATION_270 = 3; 76 77 /** 78 * Invalid surface group ID. 79 * 80 *<p>An {@link OutputConfiguration} with this value indicates that the included surface 81 *doesn't belong to any surface group.</p> 82 */ 83 public static final int SURFACE_GROUP_ID_NONE = -1; 84 85 /** 86 * Create a new {@link OutputConfiguration} instance with a {@link Surface}. 87 * 88 * @param surface 89 * A Surface for camera to output to. 90 * 91 * <p>This constructor creates a default configuration, with a surface group ID of 92 * {@value #SURFACE_GROUP_ID_NONE}.</p> 93 * 94 */ OutputConfiguration(@onNull Surface surface)95 public OutputConfiguration(@NonNull Surface surface) { 96 this(SURFACE_GROUP_ID_NONE, surface, ROTATION_0); 97 } 98 99 /** 100 * Unknown surface source type. 101 */ 102 private final int SURFACE_TYPE_UNKNOWN = -1; 103 104 /** 105 * The surface is obtained from {@link android.view.SurfaceView}. 106 */ 107 private final int SURFACE_TYPE_SURFACE_VIEW = 0; 108 109 /** 110 * The surface is obtained from {@link android.graphics.SurfaceTexture}. 111 */ 112 private final int SURFACE_TYPE_SURFACE_TEXTURE = 1; 113 114 /** 115 * Create a new {@link OutputConfiguration} instance with a {@link Surface}, 116 * with a surface group ID. 117 * 118 * <p> 119 * A surface group ID is used to identify which surface group this output surface belongs to. A 120 * surface group is a group of output surfaces that are not intended to receive camera output 121 * buffer streams simultaneously. The {@link CameraDevice} may be able to share the buffers used 122 * by all the surfaces from the same surface group, therefore may reduce the overall memory 123 * footprint. The application should only set the same set ID for the streams that are not 124 * simultaneously streaming. A negative ID indicates that this surface doesn't belong to any 125 * surface group. The default value is {@value #SURFACE_GROUP_ID_NONE}.</p> 126 * 127 * <p>For example, a video chat application that has an adaptive output resolution feature would 128 * need two (or more) output resolutions, to switch resolutions without any output glitches. 129 * However, at any given time, only one output is active to minimize outgoing network bandwidth 130 * and encoding overhead. To save memory, the application should set the video outputs to have 131 * the same non-negative group ID, so that the camera device can share the same memory region 132 * for the alternating outputs.</p> 133 * 134 * <p>It is not an error to include output streams with the same group ID in the same capture 135 * request, but the resulting memory consumption may be higher than if the two streams were 136 * not in the same surface group to begin with, especially if the outputs have substantially 137 * different dimensions.</p> 138 * 139 * @param surfaceGroupId 140 * A group ID for this output, used for sharing memory between multiple outputs. 141 * @param surface 142 * A Surface for camera to output to. 143 * 144 */ OutputConfiguration(int surfaceGroupId, @NonNull Surface surface)145 public OutputConfiguration(int surfaceGroupId, @NonNull Surface surface) { 146 this(surfaceGroupId, surface, ROTATION_0); 147 } 148 149 /** 150 * Create a new {@link OutputConfiguration} instance. 151 * 152 * <p>This constructor takes an argument for desired camera rotation</p> 153 * 154 * @param surface 155 * A Surface for camera to output to. 156 * @param rotation 157 * The desired rotation to be applied on camera output. Value must be one of 158 * ROTATION_[0, 90, 180, 270]. Note that when the rotation is 90 or 270 degrees, 159 * application should make sure corresponding surface size has width and height 160 * transposed relative to the width and height without rotation. For example, 161 * if application needs camera to capture 1280x720 picture and rotate it by 90 degree, 162 * application should set rotation to {@code ROTATION_90} and make sure the 163 * corresponding Surface size is 720x1280. Note that {@link CameraDevice} might 164 * throw {@code IllegalArgumentException} if device cannot perform such rotation. 165 * @hide 166 */ 167 @SystemApi OutputConfiguration(@onNull Surface surface, int rotation)168 public OutputConfiguration(@NonNull Surface surface, int rotation) { 169 this(SURFACE_GROUP_ID_NONE, surface, rotation); 170 } 171 172 173 /** 174 * Create a new {@link OutputConfiguration} instance, with rotation and a group ID. 175 * 176 * <p>This constructor takes an argument for desired camera rotation and for the surface group 177 * ID. See {@link #OutputConfiguration(int, Surface)} for details of the group ID.</p> 178 * 179 * @param surfaceGroupId 180 * A group ID for this output, used for sharing memory between multiple outputs. 181 * @param surface 182 * A Surface for camera to output to. 183 * @param rotation 184 * The desired rotation to be applied on camera output. Value must be one of 185 * ROTATION_[0, 90, 180, 270]. Note that when the rotation is 90 or 270 degrees, 186 * application should make sure corresponding surface size has width and height 187 * transposed relative to the width and height without rotation. For example, 188 * if application needs camera to capture 1280x720 picture and rotate it by 90 degree, 189 * application should set rotation to {@code ROTATION_90} and make sure the 190 * corresponding Surface size is 720x1280. Note that {@link CameraDevice} might 191 * throw {@code IllegalArgumentException} if device cannot perform such rotation. 192 * @hide 193 */ 194 @SystemApi OutputConfiguration(int surfaceGroupId, @NonNull Surface surface, int rotation)195 public OutputConfiguration(int surfaceGroupId, @NonNull Surface surface, int rotation) { 196 checkNotNull(surface, "Surface must not be null"); 197 checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant"); 198 mSurfaceGroupId = surfaceGroupId; 199 mSurfaceType = SURFACE_TYPE_UNKNOWN; 200 mSurface = surface; 201 mRotation = rotation; 202 mConfiguredSize = SurfaceUtils.getSurfaceSize(surface); 203 mConfiguredFormat = SurfaceUtils.getSurfaceFormat(surface); 204 mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(surface); 205 mConfiguredGenerationId = surface.getGenerationId(); 206 mIsDeferredConfig = false; 207 } 208 209 /** 210 * Create a new {@link OutputConfiguration} instance, with desired Surface size and Surface 211 * source class. 212 * <p> 213 * This constructor takes an argument for desired Surface size and the Surface source class 214 * without providing the actual output Surface. This is used to setup a output configuration 215 * with a deferred Surface. The application can use this output configuration to create a 216 * session. 217 * </p> 218 * <p> 219 * However, the actual output Surface must be set via {@link #setDeferredSurface} and finish the 220 * deferred Surface configuration via {@link CameraCaptureSession#finishDeferredConfiguration} 221 * before submitting a request with this Surface target. The deferred Surface can only be 222 * obtained from either from {@link android.view.SurfaceView} by calling 223 * {@link android.view.SurfaceHolder#getSurface}, or from 224 * {@link android.graphics.SurfaceTexture} via 225 * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}). 226 * </p> 227 * 228 * @param surfaceSize Size for the deferred surface. 229 * @param klass a non-{@code null} {@link Class} object reference that indicates the source of 230 * this surface. Only {@link android.view.SurfaceHolder SurfaceHolder.class} and 231 * {@link android.graphics.SurfaceTexture SurfaceTexture.class} are supported. 232 * @hide 233 */ OutputConfiguration(@onNull Size surfaceSize, @NonNull Class<T> klass)234 public <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass) { 235 checkNotNull(klass, "surfaceSize must not be null"); 236 checkNotNull(klass, "klass must not be null"); 237 if (klass == android.view.SurfaceHolder.class) { 238 mSurfaceType = SURFACE_TYPE_SURFACE_VIEW; 239 } else if (klass == android.graphics.SurfaceTexture.class) { 240 mSurfaceType = SURFACE_TYPE_SURFACE_TEXTURE; 241 } else { 242 mSurfaceType = SURFACE_TYPE_UNKNOWN; 243 throw new IllegalArgumentException("Unknow surface source class type"); 244 } 245 246 mSurfaceGroupId = SURFACE_GROUP_ID_NONE; 247 mSurface = null; 248 mRotation = ROTATION_0; 249 mConfiguredSize = surfaceSize; 250 mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE); 251 mConfiguredDataspace = StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE); 252 mConfiguredGenerationId = 0; 253 mIsDeferredConfig = true; 254 } 255 256 /** 257 * Check if this configuration has deferred configuration. 258 * 259 * <p>This will return true if the output configuration was constructed with surface deferred. 260 * It will return true even after the deferred surface is set later.</p> 261 * 262 * @return true if this configuration has deferred surface. 263 * @hide 264 */ isDeferredConfiguration()265 public boolean isDeferredConfiguration() { 266 return mIsDeferredConfig; 267 } 268 269 /** 270 * Set the deferred surface to this OutputConfiguration. 271 * 272 * <p> 273 * The deferred surface must be obtained from either from {@link android.view.SurfaceView} by 274 * calling {@link android.view.SurfaceHolder#getSurface}, or from 275 * {@link android.graphics.SurfaceTexture} via 276 * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}). After the deferred 277 * surface is set, the application must finish the deferred surface configuration via 278 * {@link CameraCaptureSession#finishDeferredConfiguration} before submitting a request with 279 * this surface target. 280 * </p> 281 * 282 * @param surface The deferred surface to be set. 283 * @throws IllegalArgumentException if the Surface is invalid. 284 * @throws IllegalStateException if a Surface was already set to this deferred 285 * OutputConfiguration. 286 * @hide 287 */ setDeferredSurface(@onNull Surface surface)288 public void setDeferredSurface(@NonNull Surface surface) { 289 checkNotNull(surface, "Surface must not be null"); 290 if (mSurface != null) { 291 throw new IllegalStateException("Deferred surface is already set!"); 292 } 293 294 // This will throw IAE is the surface was abandoned. 295 Size surfaceSize = SurfaceUtils.getSurfaceSize(surface); 296 if (!surfaceSize.equals(mConfiguredSize)) { 297 Log.w(TAG, "Deferred surface size " + surfaceSize + 298 " is different with pre-configured size " + mConfiguredSize + 299 ", the pre-configured size will be used."); 300 } 301 302 mSurface = surface; 303 } 304 305 /** 306 * Create a new {@link OutputConfiguration} instance with another {@link OutputConfiguration} 307 * instance. 308 * 309 * @param other Another {@link OutputConfiguration} instance to be copied. 310 * 311 * @hide 312 */ OutputConfiguration(@onNull OutputConfiguration other)313 public OutputConfiguration(@NonNull OutputConfiguration other) { 314 if (other == null) { 315 throw new IllegalArgumentException("OutputConfiguration shouldn't be null"); 316 } 317 318 this.mSurface = other.mSurface; 319 this.mRotation = other.mRotation; 320 this.mSurfaceGroupId = other.mSurfaceGroupId; 321 this.mSurfaceType = other.mSurfaceType; 322 this.mConfiguredDataspace = other.mConfiguredDataspace; 323 this.mConfiguredFormat = other.mConfiguredFormat; 324 this.mConfiguredSize = other.mConfiguredSize; 325 this.mConfiguredGenerationId = other.mConfiguredGenerationId; 326 this.mIsDeferredConfig = other.mIsDeferredConfig; 327 } 328 329 /** 330 * Create an OutputConfiguration from Parcel. 331 */ OutputConfiguration(@onNull Parcel source)332 private OutputConfiguration(@NonNull Parcel source) { 333 int rotation = source.readInt(); 334 int surfaceSetId = source.readInt(); 335 int surfaceType = source.readInt(); 336 int width = source.readInt(); 337 int height = source.readInt(); 338 Surface surface = Surface.CREATOR.createFromParcel(source); 339 checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant"); 340 mSurfaceGroupId = surfaceSetId; 341 mSurface = surface; 342 mRotation = rotation; 343 if (surface != null) { 344 mSurfaceType = SURFACE_TYPE_UNKNOWN; 345 mConfiguredSize = SurfaceUtils.getSurfaceSize(mSurface); 346 mConfiguredFormat = SurfaceUtils.getSurfaceFormat(mSurface); 347 mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(mSurface); 348 mConfiguredGenerationId = mSurface.getGenerationId(); 349 mIsDeferredConfig = true; 350 } else { 351 mSurfaceType = surfaceType; 352 mConfiguredSize = new Size(width, height); 353 mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE); 354 mConfiguredGenerationId = 0; 355 mConfiguredDataspace = 356 StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE); 357 mIsDeferredConfig = false; 358 } 359 } 360 361 /** 362 * Get the {@link Surface} associated with this {@link OutputConfiguration}. 363 * 364 * @return the {@link Surface} associated with this {@link OutputConfiguration}. 365 */ 366 @NonNull getSurface()367 public Surface getSurface() { 368 return mSurface; 369 } 370 371 /** 372 * Get the rotation associated with this {@link OutputConfiguration}. 373 * 374 * @return the rotation associated with this {@link OutputConfiguration}. 375 * Value will be one of ROTATION_[0, 90, 180, 270] 376 * 377 * @hide 378 */ 379 @SystemApi getRotation()380 public int getRotation() { 381 return mRotation; 382 } 383 384 /** 385 * Get the surface group ID associated with this {@link OutputConfiguration}. 386 * 387 * @return the surface group ID associated with this {@link OutputConfiguration}. 388 * The default value is {@value #SURFACE_GROUP_ID_NONE}. 389 */ getSurfaceGroupId()390 public int getSurfaceGroupId() { 391 return mSurfaceGroupId; 392 } 393 394 public static final Parcelable.Creator<OutputConfiguration> CREATOR = 395 new Parcelable.Creator<OutputConfiguration>() { 396 @Override 397 public OutputConfiguration createFromParcel(Parcel source) { 398 try { 399 OutputConfiguration outputConfiguration = new OutputConfiguration(source); 400 return outputConfiguration; 401 } catch (Exception e) { 402 Log.e(TAG, "Exception creating OutputConfiguration from parcel", e); 403 return null; 404 } 405 } 406 407 @Override 408 public OutputConfiguration[] newArray(int size) { 409 return new OutputConfiguration[size]; 410 } 411 }; 412 413 @Override describeContents()414 public int describeContents() { 415 return 0; 416 } 417 418 @Override writeToParcel(Parcel dest, int flags)419 public void writeToParcel(Parcel dest, int flags) { 420 if (dest == null) { 421 throw new IllegalArgumentException("dest must not be null"); 422 } 423 dest.writeInt(mRotation); 424 dest.writeInt(mSurfaceGroupId); 425 dest.writeInt(mSurfaceType); 426 dest.writeInt(mConfiguredSize.getWidth()); 427 dest.writeInt(mConfiguredSize.getHeight()); 428 if (mSurface != null) { 429 mSurface.writeToParcel(dest, flags); 430 } 431 } 432 433 /** 434 * Check if this {@link OutputConfiguration} is equal to another {@link OutputConfiguration}. 435 * 436 * <p>Two output configurations are only equal if and only if the underlying surfaces, surface 437 * properties (width, height, format, dataspace) when the output configurations are created, 438 * and all other configuration parameters are equal. </p> 439 * 440 * @return {@code true} if the objects were equal, {@code false} otherwise 441 */ 442 @Override equals(Object obj)443 public boolean equals(Object obj) { 444 if (obj == null) { 445 return false; 446 } else if (this == obj) { 447 return true; 448 } else if (obj instanceof OutputConfiguration) { 449 final OutputConfiguration other = (OutputConfiguration) obj; 450 boolean iSSurfaceEqual = mSurface == other.mSurface && 451 mConfiguredGenerationId == other.mConfiguredGenerationId ; 452 if (mIsDeferredConfig) { 453 Log.i(TAG, "deferred config has the same surface"); 454 iSSurfaceEqual = true; 455 } 456 return mRotation == other.mRotation && 457 iSSurfaceEqual&& 458 mConfiguredSize.equals(other.mConfiguredSize) && 459 mConfiguredFormat == other.mConfiguredFormat && 460 mConfiguredDataspace == other.mConfiguredDataspace && 461 mSurfaceGroupId == other.mSurfaceGroupId && 462 mSurfaceType == other.mSurfaceType && 463 mIsDeferredConfig == other.mIsDeferredConfig; 464 } 465 return false; 466 } 467 468 /** 469 * {@inheritDoc} 470 */ 471 @Override hashCode()472 public int hashCode() { 473 // Need ensure that the hashcode remains unchanged after set a deferred surface. Otherwise 474 // The deferred output configuration will be lost in the camera streammap after the deferred 475 // surface is set. 476 if (mIsDeferredConfig) { 477 return HashCodeHelpers.hashCode( 478 mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, 479 mSurfaceGroupId, mSurfaceType); 480 } 481 482 return HashCodeHelpers.hashCode( 483 mRotation, mSurface.hashCode(), mConfiguredGenerationId, 484 mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, mSurfaceGroupId); 485 } 486 487 private static final String TAG = "OutputConfiguration"; 488 private Surface mSurface; 489 private final int mRotation; 490 private final int mSurfaceGroupId; 491 // Surface source type, this is only used by the deferred surface configuration objects. 492 private final int mSurfaceType; 493 494 // The size, format, and dataspace of the surface when OutputConfiguration is created. 495 private final Size mConfiguredSize; 496 private final int mConfiguredFormat; 497 private final int mConfiguredDataspace; 498 // Surface generation ID to distinguish changes to Surface native internals 499 private final int mConfiguredGenerationId; 500 // Flag indicating if this config has deferred surface. 501 private final boolean mIsDeferredConfig; 502 } 503