CameraDeviceImpl.java revision 0953b6461aed9770e0e537b40988e97582d74dbd
1/* 2 * Copyright (C) 2013 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 android.hardware.camera2.impl; 18 19import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE; 20 21import android.graphics.ImageFormat; 22import android.hardware.camera2.CameraAccessException; 23import android.hardware.camera2.CameraCaptureSession; 24import android.hardware.camera2.CameraCharacteristics; 25import android.hardware.camera2.CameraDevice; 26import android.hardware.camera2.CaptureRequest; 27import android.hardware.camera2.CaptureResult; 28import android.hardware.camera2.CaptureFailure; 29import android.hardware.camera2.ICameraDeviceCallbacks; 30import android.hardware.camera2.ICameraDeviceUser; 31import android.hardware.camera2.TotalCaptureResult; 32import android.hardware.camera2.params.InputConfiguration; 33import android.hardware.camera2.params.OutputConfiguration; 34import android.hardware.camera2.params.ReprocessFormatsMap; 35import android.hardware.camera2.params.StreamConfigurationMap; 36import android.hardware.camera2.utils.CameraBinderDecorator; 37import android.hardware.camera2.utils.CameraRuntimeException; 38import android.hardware.camera2.utils.LongParcelable; 39import android.hardware.camera2.utils.SurfaceUtils; 40import android.os.Handler; 41import android.os.IBinder; 42import android.os.Looper; 43import android.os.RemoteException; 44import android.util.Log; 45import android.util.Range; 46import android.util.Size; 47import android.util.SparseArray; 48import android.view.Surface; 49 50import java.util.AbstractMap.SimpleEntry; 51import java.util.ArrayList; 52import java.util.Arrays; 53import java.util.Collection; 54import java.util.Collections; 55import java.util.concurrent.atomic.AtomicBoolean; 56import java.util.HashMap; 57import java.util.HashSet; 58import java.util.Iterator; 59import java.util.List; 60import java.util.LinkedList; 61import java.util.TreeMap; 62 63/** 64 * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate 65 */ 66public class CameraDeviceImpl extends CameraDevice { 67 private final String TAG; 68 private final boolean DEBUG = false; 69 70 private static final int REQUEST_ID_NONE = -1; 71 72 // TODO: guard every function with if (!mRemoteDevice) check (if it was closed) 73 private ICameraDeviceUser mRemoteDevice; 74 75 // Lock to synchronize cross-thread access to device public interface 76 final Object mInterfaceLock = new Object(); // access from this class and Session only! 77 private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks(); 78 79 private final StateCallback mDeviceCallback; 80 private volatile StateCallbackKK mSessionStateCallback; 81 private final Handler mDeviceHandler; 82 83 private final AtomicBoolean mClosing = new AtomicBoolean(); 84 private boolean mInError = false; 85 private boolean mIdle = true; 86 87 /** map request IDs to callback/request data */ 88 private final SparseArray<CaptureCallbackHolder> mCaptureCallbackMap = 89 new SparseArray<CaptureCallbackHolder>(); 90 91 private int mRepeatingRequestId = REQUEST_ID_NONE; 92 private final ArrayList<Integer> mRepeatingRequestIdDeletedList = new ArrayList<Integer>(); 93 // Map stream IDs to input/output configurations 94 private SimpleEntry<Integer, InputConfiguration> mConfiguredInput = 95 new SimpleEntry<>(REQUEST_ID_NONE, null); 96 private final SparseArray<OutputConfiguration> mConfiguredOutputs = 97 new SparseArray<>(); 98 99 private final String mCameraId; 100 private final CameraCharacteristics mCharacteristics; 101 private final int mTotalPartialCount; 102 103 /** 104 * A list tracking request and its expected last regular frame number and last reprocess frame 105 * number. Updated when calling ICameraDeviceUser methods. 106 */ 107 private final List<RequestLastFrameNumbersHolder> mRequestLastFrameNumbersList = 108 new ArrayList<>(); 109 110 /** 111 * An object tracking received frame numbers. 112 * Updated when receiving callbacks from ICameraDeviceCallbacks. 113 */ 114 private final FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker(); 115 116 private CameraCaptureSessionImpl mCurrentSession; 117 private int mNextSessionId = 0; 118 119 // Runnables for all state transitions, except error, which needs the 120 // error code argument 121 122 private final Runnable mCallOnOpened = new Runnable() { 123 @Override 124 public void run() { 125 StateCallbackKK sessionCallback = null; 126 synchronized(mInterfaceLock) { 127 if (mRemoteDevice == null) return; // Camera already closed 128 129 sessionCallback = mSessionStateCallback; 130 } 131 if (sessionCallback != null) { 132 sessionCallback.onOpened(CameraDeviceImpl.this); 133 } 134 mDeviceCallback.onOpened(CameraDeviceImpl.this); 135 } 136 }; 137 138 private final Runnable mCallOnUnconfigured = new Runnable() { 139 @Override 140 public void run() { 141 StateCallbackKK sessionCallback = null; 142 synchronized(mInterfaceLock) { 143 if (mRemoteDevice == null) return; // Camera already closed 144 145 sessionCallback = mSessionStateCallback; 146 } 147 if (sessionCallback != null) { 148 sessionCallback.onUnconfigured(CameraDeviceImpl.this); 149 } 150 } 151 }; 152 153 private final Runnable mCallOnActive = new Runnable() { 154 @Override 155 public void run() { 156 StateCallbackKK sessionCallback = null; 157 synchronized(mInterfaceLock) { 158 if (mRemoteDevice == null) return; // Camera already closed 159 160 sessionCallback = mSessionStateCallback; 161 } 162 if (sessionCallback != null) { 163 sessionCallback.onActive(CameraDeviceImpl.this); 164 } 165 } 166 }; 167 168 private final Runnable mCallOnBusy = new Runnable() { 169 @Override 170 public void run() { 171 StateCallbackKK sessionCallback = null; 172 synchronized(mInterfaceLock) { 173 if (mRemoteDevice == null) return; // Camera already closed 174 175 sessionCallback = mSessionStateCallback; 176 } 177 if (sessionCallback != null) { 178 sessionCallback.onBusy(CameraDeviceImpl.this); 179 } 180 } 181 }; 182 183 private final Runnable mCallOnClosed = new Runnable() { 184 private boolean mClosedOnce = false; 185 186 @Override 187 public void run() { 188 if (mClosedOnce) { 189 throw new AssertionError("Don't post #onClosed more than once"); 190 } 191 StateCallbackKK sessionCallback = null; 192 synchronized(mInterfaceLock) { 193 sessionCallback = mSessionStateCallback; 194 } 195 if (sessionCallback != null) { 196 sessionCallback.onClosed(CameraDeviceImpl.this); 197 } 198 mDeviceCallback.onClosed(CameraDeviceImpl.this); 199 mClosedOnce = true; 200 } 201 }; 202 203 private final Runnable mCallOnIdle = new Runnable() { 204 @Override 205 public void run() { 206 StateCallbackKK sessionCallback = null; 207 synchronized(mInterfaceLock) { 208 if (mRemoteDevice == null) return; // Camera already closed 209 210 sessionCallback = mSessionStateCallback; 211 } 212 if (sessionCallback != null) { 213 sessionCallback.onIdle(CameraDeviceImpl.this); 214 } 215 } 216 }; 217 218 private final Runnable mCallOnDisconnected = new Runnable() { 219 @Override 220 public void run() { 221 StateCallbackKK sessionCallback = null; 222 synchronized(mInterfaceLock) { 223 if (mRemoteDevice == null) return; // Camera already closed 224 225 sessionCallback = mSessionStateCallback; 226 } 227 if (sessionCallback != null) { 228 sessionCallback.onDisconnected(CameraDeviceImpl.this); 229 } 230 mDeviceCallback.onDisconnected(CameraDeviceImpl.this); 231 } 232 }; 233 234 public CameraDeviceImpl(String cameraId, StateCallback callback, Handler handler, 235 CameraCharacteristics characteristics) { 236 if (cameraId == null || callback == null || handler == null || characteristics == null) { 237 throw new IllegalArgumentException("Null argument given"); 238 } 239 mCameraId = cameraId; 240 mDeviceCallback = callback; 241 mDeviceHandler = handler; 242 mCharacteristics = characteristics; 243 244 final int MAX_TAG_LEN = 23; 245 String tag = String.format("CameraDevice-JV-%s", mCameraId); 246 if (tag.length() > MAX_TAG_LEN) { 247 tag = tag.substring(0, MAX_TAG_LEN); 248 } 249 TAG = tag; 250 251 Integer partialCount = 252 mCharacteristics.get(CameraCharacteristics.REQUEST_PARTIAL_RESULT_COUNT); 253 if (partialCount == null) { 254 // 1 means partial result is not supported. 255 mTotalPartialCount = 1; 256 } else { 257 mTotalPartialCount = partialCount; 258 } 259 } 260 261 public CameraDeviceCallbacks getCallbacks() { 262 return mCallbacks; 263 } 264 265 public void setRemoteDevice(ICameraDeviceUser remoteDevice) { 266 synchronized(mInterfaceLock) { 267 // TODO: Move from decorator to direct binder-mediated exceptions 268 // If setRemoteFailure already called, do nothing 269 if (mInError) return; 270 271 mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice); 272 273 mDeviceHandler.post(mCallOnOpened); 274 mDeviceHandler.post(mCallOnUnconfigured); 275 } 276 } 277 278 /** 279 * Call to indicate failed connection to a remote camera device. 280 * 281 * <p>This places the camera device in the error state and informs the callback. 282 * Use in place of setRemoteDevice() when startup fails.</p> 283 */ 284 public void setRemoteFailure(final CameraRuntimeException failure) { 285 int failureCode = StateCallback.ERROR_CAMERA_DEVICE; 286 boolean failureIsError = true; 287 288 switch (failure.getReason()) { 289 case CameraAccessException.CAMERA_IN_USE: 290 failureCode = StateCallback.ERROR_CAMERA_IN_USE; 291 break; 292 case CameraAccessException.MAX_CAMERAS_IN_USE: 293 failureCode = StateCallback.ERROR_MAX_CAMERAS_IN_USE; 294 break; 295 case CameraAccessException.CAMERA_DISABLED: 296 failureCode = StateCallback.ERROR_CAMERA_DISABLED; 297 break; 298 case CameraAccessException.CAMERA_DISCONNECTED: 299 failureIsError = false; 300 break; 301 case CameraAccessException.CAMERA_ERROR: 302 failureCode = StateCallback.ERROR_CAMERA_DEVICE; 303 break; 304 default: 305 Log.wtf(TAG, "Unknown failure in opening camera device: " + failure.getReason()); 306 break; 307 } 308 final int code = failureCode; 309 final boolean isError = failureIsError; 310 synchronized(mInterfaceLock) { 311 mInError = true; 312 mDeviceHandler.post(new Runnable() { 313 @Override 314 public void run() { 315 if (isError) { 316 mDeviceCallback.onError(CameraDeviceImpl.this, code); 317 } else { 318 mDeviceCallback.onDisconnected(CameraDeviceImpl.this); 319 } 320 } 321 }); 322 } 323 } 324 325 @Override 326 public String getId() { 327 return mCameraId; 328 } 329 330 public void configureOutputs(List<Surface> outputs) throws CameraAccessException { 331 // Leave this here for backwards compatibility with older code using this directly 332 ArrayList<OutputConfiguration> outputConfigs = new ArrayList<>(outputs.size()); 333 for (Surface s : outputs) { 334 outputConfigs.add(new OutputConfiguration(s)); 335 } 336 configureStreamsChecked(/*inputConfig*/null, outputConfigs, 337 /*isConstrainedHighSpeed*/false); 338 339 } 340 341 /** 342 * Attempt to configure the input and outputs; the device goes to idle and then configures the 343 * new input and outputs if possible. 344 * 345 * <p>The configuration may gracefully fail, if input configuration is not supported, 346 * if there are too many outputs, if the formats are not supported, or if the sizes for that 347 * format is not supported. In this case this function will return {@code false} and the 348 * unconfigured callback will be fired.</p> 349 * 350 * <p>If the configuration succeeds (with 1 or more outputs with or without an input), 351 * then the idle callback is fired. Unconfiguring the device always fires the idle callback.</p> 352 * 353 * @param inputConfig input configuration or {@code null} for no input 354 * @param outputs a list of one or more surfaces, or {@code null} to unconfigure 355 * @param isConstrainedHighSpeed If the streams configuration is for constrained high speed output. 356 * @return whether or not the configuration was successful 357 * 358 * @throws CameraAccessException if there were any unexpected problems during configuration 359 */ 360 public boolean configureStreamsChecked(InputConfiguration inputConfig, 361 List<OutputConfiguration> outputs, boolean isConstrainedHighSpeed) 362 throws CameraAccessException { 363 // Treat a null input the same an empty list 364 if (outputs == null) { 365 outputs = new ArrayList<OutputConfiguration>(); 366 } 367 if (outputs.size() == 0 && inputConfig != null) { 368 throw new IllegalArgumentException("cannot configure an input stream without " + 369 "any output streams"); 370 } 371 372 checkInputConfiguration(inputConfig); 373 374 boolean success = false; 375 376 synchronized(mInterfaceLock) { 377 checkIfCameraClosedOrInError(); 378 // Streams to create 379 HashSet<OutputConfiguration> addSet = new HashSet<OutputConfiguration>(outputs); 380 // Streams to delete 381 List<Integer> deleteList = new ArrayList<Integer>(); 382 383 // Determine which streams need to be created, which to be deleted 384 for (int i = 0; i < mConfiguredOutputs.size(); ++i) { 385 int streamId = mConfiguredOutputs.keyAt(i); 386 OutputConfiguration outConfig = mConfiguredOutputs.valueAt(i); 387 388 if (!outputs.contains(outConfig)) { 389 deleteList.add(streamId); 390 } else { 391 addSet.remove(outConfig); // Don't create a stream previously created 392 } 393 } 394 395 mDeviceHandler.post(mCallOnBusy); 396 stopRepeating(); 397 398 try { 399 waitUntilIdle(); 400 401 mRemoteDevice.beginConfigure(); 402 403 // reconfigure the input stream if the input configuration is different. 404 InputConfiguration currentInputConfig = mConfiguredInput.getValue(); 405 if (inputConfig != currentInputConfig && 406 (inputConfig == null || !inputConfig.equals(currentInputConfig))) { 407 if (currentInputConfig != null) { 408 mRemoteDevice.deleteStream(mConfiguredInput.getKey()); 409 mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>( 410 REQUEST_ID_NONE, null); 411 } 412 if (inputConfig != null) { 413 int streamId = mRemoteDevice.createInputStream(inputConfig.getWidth(), 414 inputConfig.getHeight(), inputConfig.getFormat()); 415 mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>( 416 streamId, inputConfig); 417 } 418 } 419 420 // Delete all streams first (to free up HW resources) 421 for (Integer streamId : deleteList) { 422 mRemoteDevice.deleteStream(streamId); 423 mConfiguredOutputs.delete(streamId); 424 } 425 426 // Add all new streams 427 for (OutputConfiguration outConfig : outputs) { 428 if (addSet.contains(outConfig)) { 429 int streamId = mRemoteDevice.createStream(outConfig); 430 mConfiguredOutputs.put(streamId, outConfig); 431 } 432 } 433 434 try { 435 mRemoteDevice.endConfigure(isConstrainedHighSpeed); 436 } 437 catch (IllegalArgumentException e) { 438 // OK. camera service can reject stream config if it's not supported by HAL 439 // This is only the result of a programmer misusing the camera2 api. 440 Log.w(TAG, "Stream configuration failed"); 441 return false; 442 } 443 444 success = true; 445 } catch (CameraRuntimeException e) { 446 if (e.getReason() == CAMERA_IN_USE) { 447 throw new IllegalStateException("The camera is currently busy." + 448 " You must wait until the previous operation completes."); 449 } 450 451 throw e.asChecked(); 452 } catch (RemoteException e) { 453 // impossible 454 return false; 455 } finally { 456 if (success && outputs.size() > 0) { 457 mDeviceHandler.post(mCallOnIdle); 458 } else { 459 // Always return to the 'unconfigured' state if we didn't hit a fatal error 460 mDeviceHandler.post(mCallOnUnconfigured); 461 } 462 } 463 } 464 465 return success; 466 } 467 468 @Override 469 public void createCaptureSession(List<Surface> outputs, 470 CameraCaptureSession.StateCallback callback, Handler handler) 471 throws CameraAccessException { 472 List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size()); 473 for (Surface surface : outputs) { 474 outConfigurations.add(new OutputConfiguration(surface)); 475 } 476 createCaptureSessionInternal(null, outConfigurations, callback, handler, 477 /*isConstrainedHighSpeed*/false); 478 } 479 480 @Override 481 public void createCaptureSessionByOutputConfiguration( 482 List<OutputConfiguration> outputConfigurations, 483 CameraCaptureSession.StateCallback callback, Handler handler) 484 throws CameraAccessException { 485 if (DEBUG) { 486 Log.d(TAG, "createCaptureSessionByOutputConfiguration"); 487 } 488 489 createCaptureSessionInternal(null, outputConfigurations, callback, handler, 490 /*isConstrainedHighSpeed*/false); 491 } 492 493 @Override 494 public void createReprocessableCaptureSession(InputConfiguration inputConfig, 495 List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler) 496 throws CameraAccessException { 497 if (DEBUG) { 498 Log.d(TAG, "createReprocessableCaptureSession"); 499 } 500 501 if (inputConfig == null) { 502 throw new IllegalArgumentException("inputConfig cannot be null when creating a " + 503 "reprocessable capture session"); 504 } 505 List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size()); 506 for (Surface surface : outputs) { 507 outConfigurations.add(new OutputConfiguration(surface)); 508 } 509 createCaptureSessionInternal(inputConfig, outConfigurations, callback, handler, 510 /*isConstrainedHighSpeed*/false); 511 } 512 513 private void createCaptureSessionInternal(InputConfiguration inputConfig, 514 List<OutputConfiguration> outputConfigurations, 515 CameraCaptureSession.StateCallback callback, Handler handler, 516 boolean isConstrainedHighSpeed) throws CameraAccessException { 517 synchronized(mInterfaceLock) { 518 if (DEBUG) { 519 Log.d(TAG, "createCaptureSessionInternal"); 520 } 521 522 checkIfCameraClosedOrInError(); 523 524 if (isConstrainedHighSpeed && inputConfig != null) { 525 throw new IllegalArgumentException("Constrained high speed session doesn't support" 526 + " input configuration yet."); 527 } 528 529 // Notify current session that it's going away, before starting camera operations 530 // After this call completes, the session is not allowed to call into CameraDeviceImpl 531 if (mCurrentSession != null) { 532 mCurrentSession.replaceSessionClose(); 533 } 534 535 // TODO: dont block for this 536 boolean configureSuccess = true; 537 CameraAccessException pendingException = null; 538 Surface input = null; 539 try { 540 // configure streams and then block until IDLE 541 configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations, 542 isConstrainedHighSpeed); 543 if (inputConfig != null) { 544 input = new Surface(); 545 mRemoteDevice.getInputSurface(/*out*/input); 546 } 547 } catch (CameraAccessException e) { 548 configureSuccess = false; 549 pendingException = e; 550 input = null; 551 if (DEBUG) { 552 Log.v(TAG, "createCaptureSession - failed with exception ", e); 553 } 554 } catch (RemoteException e) { 555 // impossible 556 return; 557 } 558 559 List<Surface> outSurfaces = new ArrayList<>(outputConfigurations.size()); 560 for (OutputConfiguration config : outputConfigurations) { 561 outSurfaces.add(config.getSurface()); 562 } 563 // Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise. 564 CameraCaptureSessionImpl newSession = 565 new CameraCaptureSessionImpl(mNextSessionId++, input, 566 outSurfaces, callback, handler, this, mDeviceHandler, 567 configureSuccess, isConstrainedHighSpeed); 568 569 // TODO: wait until current session closes, then create the new session 570 mCurrentSession = newSession; 571 572 if (pendingException != null) { 573 throw pendingException; 574 } 575 576 mSessionStateCallback = mCurrentSession.getDeviceStateCallback(); 577 } 578 } 579 580 /** 581 * For use by backwards-compatibility code only. 582 */ 583 public void setSessionListener(StateCallbackKK sessionCallback) { 584 synchronized(mInterfaceLock) { 585 mSessionStateCallback = sessionCallback; 586 } 587 } 588 589 @Override 590 public CaptureRequest.Builder createCaptureRequest(int templateType) 591 throws CameraAccessException { 592 synchronized(mInterfaceLock) { 593 checkIfCameraClosedOrInError(); 594 595 CameraMetadataNative templatedRequest = new CameraMetadataNative(); 596 597 try { 598 mRemoteDevice.createDefaultRequest(templateType, /*out*/templatedRequest); 599 } catch (CameraRuntimeException e) { 600 throw e.asChecked(); 601 } catch (RemoteException e) { 602 // impossible 603 return null; 604 } 605 606 CaptureRequest.Builder builder = new CaptureRequest.Builder( 607 templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE); 608 609 return builder; 610 } 611 } 612 613 @Override 614 public CaptureRequest.Builder createReprocessCaptureRequest(TotalCaptureResult inputResult) 615 throws CameraAccessException { 616 synchronized(mInterfaceLock) { 617 checkIfCameraClosedOrInError(); 618 619 CameraMetadataNative resultMetadata = new 620 CameraMetadataNative(inputResult.getNativeCopy()); 621 622 return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true, 623 inputResult.getSessionId()); 624 } 625 } 626 627 public void prepare(Surface surface) throws CameraAccessException { 628 if (surface == null) throw new IllegalArgumentException("Surface is null"); 629 630 synchronized(mInterfaceLock) { 631 int streamId = -1; 632 for (int i = 0; i < mConfiguredOutputs.size(); i++) { 633 if (surface == mConfiguredOutputs.valueAt(i).getSurface()) { 634 streamId = mConfiguredOutputs.keyAt(i); 635 break; 636 } 637 } 638 if (streamId == -1) { 639 throw new IllegalArgumentException("Surface is not part of this session"); 640 } 641 try { 642 mRemoteDevice.prepare(streamId); 643 } catch (CameraRuntimeException e) { 644 throw e.asChecked(); 645 } catch (RemoteException e) { 646 // impossible 647 return; 648 } 649 } 650 } 651 652 public int capture(CaptureRequest request, CaptureCallback callback, Handler handler) 653 throws CameraAccessException { 654 if (DEBUG) { 655 Log.d(TAG, "calling capture"); 656 } 657 List<CaptureRequest> requestList = new ArrayList<CaptureRequest>(); 658 requestList.add(request); 659 return submitCaptureRequest(requestList, callback, handler, /*streaming*/false); 660 } 661 662 public int captureBurst(List<CaptureRequest> requests, CaptureCallback callback, 663 Handler handler) throws CameraAccessException { 664 if (requests == null || requests.isEmpty()) { 665 throw new IllegalArgumentException("At least one request must be given"); 666 } 667 return submitCaptureRequest(requests, callback, handler, /*streaming*/false); 668 } 669 670 /** 671 * This method checks lastFrameNumber returned from ICameraDeviceUser methods for 672 * starting and stopping repeating request and flushing. 673 * 674 * <p>If lastFrameNumber is NO_FRAMES_CAPTURED, it means that the request was never 675 * sent to HAL. Then onCaptureSequenceAborted is immediately triggered. 676 * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber as the last 677 * regular frame number will be added to the list mRequestLastFrameNumbersList.</p> 678 * 679 * @param requestId the request ID of the current repeating request. 680 * 681 * @param lastFrameNumber last frame number returned from binder. 682 */ 683 private void checkEarlyTriggerSequenceComplete( 684 final int requestId, final long lastFrameNumber) { 685 // lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request 686 // was never sent to HAL. Should trigger onCaptureSequenceAborted immediately. 687 if (lastFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) { 688 final CaptureCallbackHolder holder; 689 int index = mCaptureCallbackMap.indexOfKey(requestId); 690 holder = (index >= 0) ? mCaptureCallbackMap.valueAt(index) : null; 691 if (holder != null) { 692 mCaptureCallbackMap.removeAt(index); 693 if (DEBUG) { 694 Log.v(TAG, String.format( 695 "remove holder for requestId %d, " 696 + "because lastFrame is %d.", 697 requestId, lastFrameNumber)); 698 } 699 } 700 701 if (holder != null) { 702 if (DEBUG) { 703 Log.v(TAG, "immediately trigger onCaptureSequenceAborted because" 704 + " request did not reach HAL"); 705 } 706 707 Runnable resultDispatch = new Runnable() { 708 @Override 709 public void run() { 710 if (!CameraDeviceImpl.this.isClosed()) { 711 if (DEBUG) { 712 Log.d(TAG, String.format( 713 "early trigger sequence complete for request %d", 714 requestId)); 715 } 716 holder.getCallback().onCaptureSequenceAborted( 717 CameraDeviceImpl.this, 718 requestId); 719 } 720 } 721 }; 722 holder.getHandler().post(resultDispatch); 723 } else { 724 Log.w(TAG, String.format( 725 "did not register callback to request %d", 726 requestId)); 727 } 728 } else { 729 // This function is only called for regular request so lastFrameNumber is the last 730 // regular frame number. 731 mRequestLastFrameNumbersList.add(new RequestLastFrameNumbersHolder(requestId, 732 lastFrameNumber)); 733 734 // It is possible that the last frame has already arrived, so we need to check 735 // for sequence completion right away 736 checkAndFireSequenceComplete(); 737 } 738 } 739 740 private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureCallback callback, 741 Handler handler, boolean repeating) throws CameraAccessException { 742 743 // Need a valid handler, or current thread needs to have a looper, if 744 // callback is valid 745 handler = checkHandler(handler, callback); 746 747 // Make sure that there all requests have at least 1 surface; all surfaces are non-null 748 for (CaptureRequest request : requestList) { 749 if (request.getTargets().isEmpty()) { 750 throw new IllegalArgumentException( 751 "Each request must have at least one Surface target"); 752 } 753 754 for (Surface surface : request.getTargets()) { 755 if (surface == null) { 756 throw new IllegalArgumentException("Null Surface targets are not allowed"); 757 } 758 } 759 } 760 761 synchronized(mInterfaceLock) { 762 checkIfCameraClosedOrInError(); 763 int requestId; 764 765 if (repeating) { 766 stopRepeating(); 767 } 768 769 LongParcelable lastFrameNumberRef = new LongParcelable(); 770 try { 771 requestId = mRemoteDevice.submitRequestList(requestList, repeating, 772 /*out*/lastFrameNumberRef); 773 if (DEBUG) { 774 Log.v(TAG, "last frame number " + lastFrameNumberRef.getNumber()); 775 } 776 } catch (CameraRuntimeException e) { 777 throw e.asChecked(); 778 } catch (RemoteException e) { 779 // impossible 780 return -1; 781 } 782 783 if (callback != null) { 784 mCaptureCallbackMap.put(requestId, new CaptureCallbackHolder(callback, 785 requestList, handler, repeating, mNextSessionId - 1)); 786 } else { 787 if (DEBUG) { 788 Log.d(TAG, "Listen for request " + requestId + " is null"); 789 } 790 } 791 792 long lastFrameNumber = lastFrameNumberRef.getNumber(); 793 794 if (repeating) { 795 if (mRepeatingRequestId != REQUEST_ID_NONE) { 796 checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber); 797 } 798 mRepeatingRequestId = requestId; 799 } else { 800 mRequestLastFrameNumbersList.add(new RequestLastFrameNumbersHolder(requestList, 801 requestId, lastFrameNumber)); 802 } 803 804 if (mIdle) { 805 mDeviceHandler.post(mCallOnActive); 806 } 807 mIdle = false; 808 809 return requestId; 810 } 811 } 812 813 public int setRepeatingRequest(CaptureRequest request, CaptureCallback callback, 814 Handler handler) throws CameraAccessException { 815 List<CaptureRequest> requestList = new ArrayList<CaptureRequest>(); 816 requestList.add(request); 817 return submitCaptureRequest(requestList, callback, handler, /*streaming*/true); 818 } 819 820 public int setRepeatingBurst(List<CaptureRequest> requests, CaptureCallback callback, 821 Handler handler) throws CameraAccessException { 822 if (requests == null || requests.isEmpty()) { 823 throw new IllegalArgumentException("At least one request must be given"); 824 } 825 return submitCaptureRequest(requests, callback, handler, /*streaming*/true); 826 } 827 828 public void stopRepeating() throws CameraAccessException { 829 830 synchronized(mInterfaceLock) { 831 checkIfCameraClosedOrInError(); 832 if (mRepeatingRequestId != REQUEST_ID_NONE) { 833 834 int requestId = mRepeatingRequestId; 835 mRepeatingRequestId = REQUEST_ID_NONE; 836 837 // Queue for deletion after in-flight requests finish 838 if (mCaptureCallbackMap.get(requestId) != null) { 839 mRepeatingRequestIdDeletedList.add(requestId); 840 } 841 842 try { 843 LongParcelable lastFrameNumberRef = new LongParcelable(); 844 mRemoteDevice.cancelRequest(requestId, /*out*/lastFrameNumberRef); 845 long lastFrameNumber = lastFrameNumberRef.getNumber(); 846 847 checkEarlyTriggerSequenceComplete(requestId, lastFrameNumber); 848 849 } catch (CameraRuntimeException e) { 850 throw e.asChecked(); 851 } catch (RemoteException e) { 852 // impossible 853 return; 854 } 855 } 856 } 857 } 858 859 private void waitUntilIdle() throws CameraAccessException { 860 861 synchronized(mInterfaceLock) { 862 checkIfCameraClosedOrInError(); 863 864 if (mRepeatingRequestId != REQUEST_ID_NONE) { 865 throw new IllegalStateException("Active repeating request ongoing"); 866 } 867 try { 868 mRemoteDevice.waitUntilIdle(); 869 } catch (CameraRuntimeException e) { 870 throw e.asChecked(); 871 } catch (RemoteException e) { 872 // impossible 873 return; 874 } 875 } 876 } 877 878 public void flush() throws CameraAccessException { 879 synchronized(mInterfaceLock) { 880 checkIfCameraClosedOrInError(); 881 882 mDeviceHandler.post(mCallOnBusy); 883 884 // If already idle, just do a busy->idle transition immediately, don't actually 885 // flush. 886 if (mIdle) { 887 mDeviceHandler.post(mCallOnIdle); 888 return; 889 } 890 try { 891 LongParcelable lastFrameNumberRef = new LongParcelable(); 892 mRemoteDevice.flush(/*out*/lastFrameNumberRef); 893 if (mRepeatingRequestId != REQUEST_ID_NONE) { 894 long lastFrameNumber = lastFrameNumberRef.getNumber(); 895 checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber); 896 mRepeatingRequestId = REQUEST_ID_NONE; 897 } 898 } catch (CameraRuntimeException e) { 899 throw e.asChecked(); 900 } catch (RemoteException e) { 901 // impossible 902 return; 903 } 904 } 905 } 906 907 @Override 908 public void close() { 909 synchronized (mInterfaceLock) { 910 if (mClosing.getAndSet(true)) { 911 return; 912 } 913 914 try { 915 if (mRemoteDevice != null) { 916 mRemoteDevice.disconnect(); 917 } 918 } catch (CameraRuntimeException e) { 919 Log.e(TAG, "Exception while closing: ", e.asChecked()); 920 } catch (RemoteException e) { 921 // impossible 922 } 923 924 // Only want to fire the onClosed callback once; 925 // either a normal close where the remote device is valid 926 // or a close after a startup error (no remote device but in error state) 927 if (mRemoteDevice != null || mInError) { 928 mDeviceHandler.post(mCallOnClosed); 929 } 930 931 mRemoteDevice = null; 932 } 933 } 934 935 @Override 936 protected void finalize() throws Throwable { 937 try { 938 close(); 939 } 940 finally { 941 super.finalize(); 942 } 943 } 944 945 private void checkInputConfiguration(InputConfiguration inputConfig) { 946 if (inputConfig != null) { 947 StreamConfigurationMap configMap = mCharacteristics.get( 948 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 949 950 int[] inputFormats = configMap.getInputFormats(); 951 boolean validFormat = false; 952 for (int format : inputFormats) { 953 if (format == inputConfig.getFormat()) { 954 validFormat = true; 955 } 956 } 957 958 if (validFormat == false) { 959 throw new IllegalArgumentException("input format " + inputConfig.getFormat() + 960 " is not valid"); 961 } 962 963 boolean validSize = false; 964 Size[] inputSizes = configMap.getInputSizes(inputConfig.getFormat()); 965 for (Size s : inputSizes) { 966 if (inputConfig.getWidth() == s.getWidth() && 967 inputConfig.getHeight() == s.getHeight()) { 968 validSize = true; 969 } 970 } 971 972 if (validSize == false) { 973 throw new IllegalArgumentException("input size " + inputConfig.getWidth() + "x" + 974 inputConfig.getHeight() + " is not valid"); 975 } 976 } 977 } 978 979 /** 980 * <p>A callback for tracking the progress of a {@link CaptureRequest} 981 * submitted to the camera device.</p> 982 * 983 */ 984 public static abstract class CaptureCallback { 985 986 /** 987 * This constant is used to indicate that no images were captured for 988 * the request. 989 * 990 * @hide 991 */ 992 public static final int NO_FRAMES_CAPTURED = -1; 993 994 /** 995 * This method is called when the camera device has started capturing 996 * the output image for the request, at the beginning of image exposure. 997 * 998 * @see android.media.MediaActionSound 999 */ 1000 public void onCaptureStarted(CameraDevice camera, 1001 CaptureRequest request, long timestamp, long frameNumber) { 1002 // default empty implementation 1003 } 1004 1005 /** 1006 * This method is called when some results from an image capture are 1007 * available. 1008 * 1009 * @hide 1010 */ 1011 public void onCapturePartial(CameraDevice camera, 1012 CaptureRequest request, CaptureResult result) { 1013 // default empty implementation 1014 } 1015 1016 /** 1017 * This method is called when an image capture makes partial forward progress; some 1018 * (but not all) results from an image capture are available. 1019 * 1020 */ 1021 public void onCaptureProgressed(CameraDevice camera, 1022 CaptureRequest request, CaptureResult partialResult) { 1023 // default empty implementation 1024 } 1025 1026 /** 1027 * This method is called when an image capture has fully completed and all the 1028 * result metadata is available. 1029 */ 1030 public void onCaptureCompleted(CameraDevice camera, 1031 CaptureRequest request, TotalCaptureResult result) { 1032 // default empty implementation 1033 } 1034 1035 /** 1036 * This method is called instead of {@link #onCaptureCompleted} when the 1037 * camera device failed to produce a {@link CaptureResult} for the 1038 * request. 1039 */ 1040 public void onCaptureFailed(CameraDevice camera, 1041 CaptureRequest request, CaptureFailure failure) { 1042 // default empty implementation 1043 } 1044 1045 /** 1046 * This method is called independently of the others in CaptureCallback, 1047 * when a capture sequence finishes and all {@link CaptureResult} 1048 * or {@link CaptureFailure} for it have been returned via this callback. 1049 */ 1050 public void onCaptureSequenceCompleted(CameraDevice camera, 1051 int sequenceId, long frameNumber) { 1052 // default empty implementation 1053 } 1054 1055 /** 1056 * This method is called independently of the others in CaptureCallback, 1057 * when a capture sequence aborts before any {@link CaptureResult} 1058 * or {@link CaptureFailure} for it have been returned via this callback. 1059 */ 1060 public void onCaptureSequenceAborted(CameraDevice camera, 1061 int sequenceId) { 1062 // default empty implementation 1063 } 1064 } 1065 1066 /** 1067 * A callback for notifications about the state of a camera device, adding in the callbacks that 1068 * were part of the earlier KK API design, but now only used internally. 1069 */ 1070 public static abstract class StateCallbackKK extends StateCallback { 1071 /** 1072 * The method called when a camera device has no outputs configured. 1073 * 1074 */ 1075 public void onUnconfigured(CameraDevice camera) { 1076 // Default empty implementation 1077 } 1078 1079 /** 1080 * The method called when a camera device begins processing 1081 * {@link CaptureRequest capture requests}. 1082 * 1083 */ 1084 public void onActive(CameraDevice camera) { 1085 // Default empty implementation 1086 } 1087 1088 /** 1089 * The method called when a camera device is busy. 1090 * 1091 */ 1092 public void onBusy(CameraDevice camera) { 1093 // Default empty implementation 1094 } 1095 1096 /** 1097 * The method called when a camera device has finished processing all 1098 * submitted capture requests and has reached an idle state. 1099 * 1100 */ 1101 public void onIdle(CameraDevice camera) { 1102 // Default empty implementation 1103 } 1104 1105 /** 1106 * The method called when the camera device has finished preparing 1107 * an output Surface 1108 */ 1109 public void onSurfacePrepared(Surface surface) { 1110 // Default empty implementation 1111 } 1112 } 1113 1114 static class CaptureCallbackHolder { 1115 1116 private final boolean mRepeating; 1117 private final CaptureCallback mCallback; 1118 private final List<CaptureRequest> mRequestList; 1119 private final Handler mHandler; 1120 private final int mSessionId; 1121 1122 CaptureCallbackHolder(CaptureCallback callback, List<CaptureRequest> requestList, 1123 Handler handler, boolean repeating, int sessionId) { 1124 if (callback == null || handler == null) { 1125 throw new UnsupportedOperationException( 1126 "Must have a valid handler and a valid callback"); 1127 } 1128 mRepeating = repeating; 1129 mHandler = handler; 1130 mRequestList = new ArrayList<CaptureRequest>(requestList); 1131 mCallback = callback; 1132 mSessionId = sessionId; 1133 } 1134 1135 public boolean isRepeating() { 1136 return mRepeating; 1137 } 1138 1139 public CaptureCallback getCallback() { 1140 return mCallback; 1141 } 1142 1143 public CaptureRequest getRequest(int subsequenceId) { 1144 if (subsequenceId >= mRequestList.size()) { 1145 throw new IllegalArgumentException( 1146 String.format( 1147 "Requested subsequenceId %d is larger than request list size %d.", 1148 subsequenceId, mRequestList.size())); 1149 } else { 1150 if (subsequenceId < 0) { 1151 throw new IllegalArgumentException(String.format( 1152 "Requested subsequenceId %d is negative", subsequenceId)); 1153 } else { 1154 return mRequestList.get(subsequenceId); 1155 } 1156 } 1157 } 1158 1159 public CaptureRequest getRequest() { 1160 return getRequest(0); 1161 } 1162 1163 public Handler getHandler() { 1164 return mHandler; 1165 } 1166 1167 public int getSessionId() { 1168 return mSessionId; 1169 } 1170 } 1171 1172 /** 1173 * This class holds a capture ID and its expected last regular frame number and last reprocess 1174 * frame number. 1175 */ 1176 static class RequestLastFrameNumbersHolder { 1177 // request ID 1178 private final int mRequestId; 1179 // The last regular frame number for this request ID. It's 1180 // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no regular request. 1181 private final long mLastRegularFrameNumber; 1182 // The last reprocess frame number for this request ID. It's 1183 // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no reprocess request. 1184 private final long mLastReprocessFrameNumber; 1185 1186 /** 1187 * Create a request-last-frame-numbers holder with a list of requests, request ID, and 1188 * the last frame number returned by camera service. 1189 */ 1190 public RequestLastFrameNumbersHolder(List<CaptureRequest> requestList, int requestId, 1191 long lastFrameNumber) { 1192 long lastRegularFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; 1193 long lastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; 1194 long frameNumber = lastFrameNumber; 1195 1196 if (lastFrameNumber < requestList.size() - 1) { 1197 throw new IllegalArgumentException("lastFrameNumber: " + lastFrameNumber + 1198 " should be at least " + (requestList.size() - 1) + " for the number of " + 1199 " requests in the list: " + requestList.size()); 1200 } 1201 1202 // find the last regular frame number and the last reprocess frame number 1203 for (int i = requestList.size() - 1; i >= 0; i--) { 1204 CaptureRequest request = requestList.get(i); 1205 if (request.isReprocess() && lastReprocessFrameNumber == 1206 CaptureCallback.NO_FRAMES_CAPTURED) { 1207 lastReprocessFrameNumber = frameNumber; 1208 } else if (!request.isReprocess() && lastRegularFrameNumber == 1209 CaptureCallback.NO_FRAMES_CAPTURED) { 1210 lastRegularFrameNumber = frameNumber; 1211 } 1212 1213 if (lastReprocessFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED && 1214 lastRegularFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED) { 1215 break; 1216 } 1217 1218 frameNumber--; 1219 } 1220 1221 mLastRegularFrameNumber = lastRegularFrameNumber; 1222 mLastReprocessFrameNumber = lastReprocessFrameNumber; 1223 mRequestId = requestId; 1224 } 1225 1226 /** 1227 * Create a request-last-frame-numbers holder with a request ID and last regular frame 1228 * number. 1229 */ 1230 public RequestLastFrameNumbersHolder(int requestId, long lastRegularFrameNumber) { 1231 mLastRegularFrameNumber = lastRegularFrameNumber; 1232 mLastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; 1233 mRequestId = requestId; 1234 } 1235 1236 /** 1237 * Return the last regular frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if 1238 * it contains no regular request. 1239 */ 1240 public long getLastRegularFrameNumber() { 1241 return mLastRegularFrameNumber; 1242 } 1243 1244 /** 1245 * Return the last reprocess frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if 1246 * it contains no reprocess request. 1247 */ 1248 public long getLastReprocessFrameNumber() { 1249 return mLastReprocessFrameNumber; 1250 } 1251 1252 /** 1253 * Return the last frame number overall. 1254 */ 1255 public long getLastFrameNumber() { 1256 return Math.max(mLastRegularFrameNumber, mLastReprocessFrameNumber); 1257 } 1258 1259 /** 1260 * Return the request ID. 1261 */ 1262 public int getRequestId() { 1263 return mRequestId; 1264 } 1265 } 1266 1267 /** 1268 * This class tracks the last frame number for submitted requests. 1269 */ 1270 public class FrameNumberTracker { 1271 1272 private long mCompletedFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; 1273 private long mCompletedReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; 1274 /** the skipped frame numbers that belong to regular results */ 1275 private final LinkedList<Long> mSkippedRegularFrameNumbers = new LinkedList<Long>(); 1276 /** the skipped frame numbers that belong to reprocess results */ 1277 private final LinkedList<Long> mSkippedReprocessFrameNumbers = new LinkedList<Long>(); 1278 /** frame number -> is reprocess */ 1279 private final TreeMap<Long, Boolean> mFutureErrorMap = new TreeMap<Long, Boolean>(); 1280 /** Map frame numbers to list of partial results */ 1281 private final HashMap<Long, List<CaptureResult>> mPartialResults = new HashMap<>(); 1282 1283 private void update() { 1284 Iterator iter = mFutureErrorMap.entrySet().iterator(); 1285 while (iter.hasNext()) { 1286 TreeMap.Entry pair = (TreeMap.Entry)iter.next(); 1287 Long errorFrameNumber = (Long)pair.getKey(); 1288 Boolean reprocess = (Boolean)pair.getValue(); 1289 Boolean removeError = true; 1290 if (reprocess) { 1291 if (errorFrameNumber == mCompletedReprocessFrameNumber + 1) { 1292 mCompletedReprocessFrameNumber = errorFrameNumber; 1293 } else if (mSkippedReprocessFrameNumbers.isEmpty() != true && 1294 errorFrameNumber == mSkippedReprocessFrameNumbers.element()) { 1295 mCompletedReprocessFrameNumber = errorFrameNumber; 1296 mSkippedReprocessFrameNumbers.remove(); 1297 } else { 1298 removeError = false; 1299 } 1300 } else { 1301 if (errorFrameNumber == mCompletedFrameNumber + 1) { 1302 mCompletedFrameNumber = errorFrameNumber; 1303 } else if (mSkippedRegularFrameNumbers.isEmpty() != true && 1304 errorFrameNumber == mSkippedRegularFrameNumbers.element()) { 1305 mCompletedFrameNumber = errorFrameNumber; 1306 mSkippedRegularFrameNumbers.remove(); 1307 } else { 1308 removeError = false; 1309 } 1310 } 1311 if (removeError) { 1312 iter.remove(); 1313 } 1314 } 1315 } 1316 1317 /** 1318 * This function is called every time when a result or an error is received. 1319 * @param frameNumber the frame number corresponding to the result or error 1320 * @param isError true if it is an error, false if it is not an error 1321 * @param isReprocess true if it is a reprocess result, false if it is a regular result. 1322 */ 1323 public void updateTracker(long frameNumber, boolean isError, boolean isReprocess) { 1324 if (isError) { 1325 mFutureErrorMap.put(frameNumber, isReprocess); 1326 } else { 1327 try { 1328 if (isReprocess) { 1329 updateCompletedReprocessFrameNumber(frameNumber); 1330 } else { 1331 updateCompletedFrameNumber(frameNumber); 1332 } 1333 } catch (IllegalArgumentException e) { 1334 Log.e(TAG, e.getMessage()); 1335 } 1336 } 1337 update(); 1338 } 1339 1340 /** 1341 * This function is called every time a result has been completed. 1342 * 1343 * <p>It keeps a track of all the partial results already created for a particular 1344 * frame number.</p> 1345 * 1346 * @param frameNumber the frame number corresponding to the result 1347 * @param result the total or partial result 1348 * @param partial {@true} if the result is partial, {@code false} if total 1349 * @param isReprocess true if it is a reprocess result, false if it is a regular result. 1350 */ 1351 public void updateTracker(long frameNumber, CaptureResult result, boolean partial, 1352 boolean isReprocess) { 1353 if (!partial) { 1354 // Update the total result's frame status as being successful 1355 updateTracker(frameNumber, /*isError*/false, isReprocess); 1356 // Don't keep a list of total results, we don't need to track them 1357 return; 1358 } 1359 1360 if (result == null) { 1361 // Do not record blank results; this also means there will be no total result 1362 // so it doesn't matter that the partials were not recorded 1363 return; 1364 } 1365 1366 // Partial results must be aggregated in-order for that frame number 1367 List<CaptureResult> partials = mPartialResults.get(frameNumber); 1368 if (partials == null) { 1369 partials = new ArrayList<>(); 1370 mPartialResults.put(frameNumber, partials); 1371 } 1372 1373 partials.add(result); 1374 } 1375 1376 /** 1377 * Attempt to pop off all of the partial results seen so far for the {@code frameNumber}. 1378 * 1379 * <p>Once popped-off, the partial results are forgotten (unless {@code updateTracker} 1380 * is called again with new partials for that frame number).</p> 1381 * 1382 * @param frameNumber the frame number corresponding to the result 1383 * @return a list of partial results for that frame with at least 1 element, 1384 * or {@code null} if there were no partials recorded for that frame 1385 */ 1386 public List<CaptureResult> popPartialResults(long frameNumber) { 1387 return mPartialResults.remove(frameNumber); 1388 } 1389 1390 public long getCompletedFrameNumber() { 1391 return mCompletedFrameNumber; 1392 } 1393 1394 public long getCompletedReprocessFrameNumber() { 1395 return mCompletedReprocessFrameNumber; 1396 } 1397 1398 /** 1399 * Update the completed frame number for regular results. 1400 * 1401 * It validates that all previous frames have arrived except for reprocess frames. 1402 * 1403 * If there is a gap since previous regular frame number, assume the frames in the gap are 1404 * reprocess frames and store them in the skipped reprocess frame number queue to check 1405 * against when reprocess frames arrive. 1406 */ 1407 private void updateCompletedFrameNumber(long frameNumber) throws IllegalArgumentException { 1408 if (frameNumber <= mCompletedFrameNumber) { 1409 throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat"); 1410 } else if (frameNumber <= mCompletedReprocessFrameNumber) { 1411 // if frame number is smaller than completed reprocess frame number, 1412 // it must be the head of mSkippedRegularFrameNumbers 1413 if (mSkippedRegularFrameNumbers.isEmpty() == true || 1414 frameNumber < mSkippedRegularFrameNumbers.element()) { 1415 throw new IllegalArgumentException("frame number " + frameNumber + 1416 " is a repeat"); 1417 } else if (frameNumber > mSkippedRegularFrameNumbers.element()) { 1418 throw new IllegalArgumentException("frame number " + frameNumber + 1419 " comes out of order. Expecting " + 1420 mSkippedRegularFrameNumbers.element()); 1421 } 1422 // frame number matches the head of the skipped frame number queue. 1423 mSkippedRegularFrameNumbers.remove(); 1424 } else { 1425 // there is a gap of unseen frame numbers which should belong to reprocess result 1426 // put all the skipped frame numbers in the queue 1427 for (long i = Math.max(mCompletedFrameNumber, mCompletedReprocessFrameNumber) + 1; 1428 i < frameNumber; i++) { 1429 mSkippedReprocessFrameNumbers.add(i); 1430 } 1431 } 1432 1433 mCompletedFrameNumber = frameNumber; 1434 } 1435 1436 /** 1437 * Update the completed frame number for reprocess results. 1438 * 1439 * It validates that all previous frames have arrived except for regular frames. 1440 * 1441 * If there is a gap since previous reprocess frame number, assume the frames in the gap are 1442 * regular frames and store them in the skipped regular frame number queue to check 1443 * against when regular frames arrive. 1444 */ 1445 private void updateCompletedReprocessFrameNumber(long frameNumber) 1446 throws IllegalArgumentException { 1447 if (frameNumber < mCompletedReprocessFrameNumber) { 1448 throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat"); 1449 } else if (frameNumber < mCompletedFrameNumber) { 1450 // if reprocess frame number is smaller than completed regular frame number, 1451 // it must be the head of the skipped reprocess frame number queue. 1452 if (mSkippedReprocessFrameNumbers.isEmpty() == true || 1453 frameNumber < mSkippedReprocessFrameNumbers.element()) { 1454 throw new IllegalArgumentException("frame number " + frameNumber + 1455 " is a repeat"); 1456 } else if (frameNumber > mSkippedReprocessFrameNumbers.element()) { 1457 throw new IllegalArgumentException("frame number " + frameNumber + 1458 " comes out of order. Expecting " + 1459 mSkippedReprocessFrameNumbers.element()); 1460 } 1461 // frame number matches the head of the skipped frame number queue. 1462 mSkippedReprocessFrameNumbers.remove(); 1463 } else { 1464 // put all the skipped frame numbers in the queue 1465 for (long i = Math.max(mCompletedFrameNumber, mCompletedReprocessFrameNumber) + 1; 1466 i < frameNumber; i++) { 1467 mSkippedRegularFrameNumbers.add(i); 1468 } 1469 } 1470 mCompletedReprocessFrameNumber = frameNumber; 1471 } 1472 } 1473 1474 private void checkAndFireSequenceComplete() { 1475 long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber(); 1476 long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber(); 1477 boolean isReprocess = false; 1478 Iterator<RequestLastFrameNumbersHolder> iter = mRequestLastFrameNumbersList.iterator(); 1479 while (iter.hasNext()) { 1480 final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next(); 1481 boolean sequenceCompleted = false; 1482 final int requestId = requestLastFrameNumbers.getRequestId(); 1483 final CaptureCallbackHolder holder; 1484 synchronized(mInterfaceLock) { 1485 if (mRemoteDevice == null) { 1486 Log.w(TAG, "Camera closed while checking sequences"); 1487 return; 1488 } 1489 1490 int index = mCaptureCallbackMap.indexOfKey(requestId); 1491 holder = (index >= 0) ? 1492 mCaptureCallbackMap.valueAt(index) : null; 1493 if (holder != null) { 1494 long lastRegularFrameNumber = 1495 requestLastFrameNumbers.getLastRegularFrameNumber(); 1496 long lastReprocessFrameNumber = 1497 requestLastFrameNumbers.getLastReprocessFrameNumber(); 1498 1499 // check if it's okay to remove request from mCaptureCallbackMap 1500 if (lastRegularFrameNumber <= completedFrameNumber && 1501 lastReprocessFrameNumber <= completedReprocessFrameNumber) { 1502 sequenceCompleted = true; 1503 mCaptureCallbackMap.removeAt(index); 1504 if (DEBUG) { 1505 Log.v(TAG, String.format( 1506 "Remove holder for requestId %d, because lastRegularFrame %d " + 1507 "is <= %d and lastReprocessFrame %d is <= %d", requestId, 1508 lastRegularFrameNumber, completedFrameNumber, 1509 lastReprocessFrameNumber, completedReprocessFrameNumber)); 1510 } 1511 } 1512 } 1513 } 1514 1515 // If no callback is registered for this requestId or sequence completed, remove it 1516 // from the frame number->request pair because it's not needed anymore. 1517 if (holder == null || sequenceCompleted) { 1518 iter.remove(); 1519 } 1520 1521 // Call onCaptureSequenceCompleted 1522 if (sequenceCompleted) { 1523 Runnable resultDispatch = new Runnable() { 1524 @Override 1525 public void run() { 1526 if (!CameraDeviceImpl.this.isClosed()){ 1527 if (DEBUG) { 1528 Log.d(TAG, String.format( 1529 "fire sequence complete for request %d", 1530 requestId)); 1531 } 1532 1533 holder.getCallback().onCaptureSequenceCompleted( 1534 CameraDeviceImpl.this, 1535 requestId, 1536 requestLastFrameNumbers.getLastFrameNumber()); 1537 } 1538 } 1539 }; 1540 holder.getHandler().post(resultDispatch); 1541 } 1542 } 1543 } 1544 1545 public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub { 1546 // 1547 // Constants below need to be kept up-to-date with 1548 // frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h 1549 // 1550 1551 // 1552 // Error codes for onCameraError 1553 // 1554 1555 /** 1556 * Camera has been disconnected 1557 */ 1558 public static final int ERROR_CAMERA_DISCONNECTED = 0; 1559 /** 1560 * Camera has encountered a device-level error 1561 * Matches CameraDevice.StateCallback#ERROR_CAMERA_DEVICE 1562 */ 1563 public static final int ERROR_CAMERA_DEVICE = 1; 1564 /** 1565 * Camera has encountered a service-level error 1566 * Matches CameraDevice.StateCallback#ERROR_CAMERA_SERVICE 1567 */ 1568 public static final int ERROR_CAMERA_SERVICE = 2; 1569 /** 1570 * Camera has encountered an error processing a single request. 1571 */ 1572 public static final int ERROR_CAMERA_REQUEST = 3; 1573 /** 1574 * Camera has encountered an error producing metadata for a single capture 1575 */ 1576 public static final int ERROR_CAMERA_RESULT = 4; 1577 /** 1578 * Camera has encountered an error producing an image buffer for a single capture 1579 */ 1580 public static final int ERROR_CAMERA_BUFFER = 5; 1581 1582 @Override 1583 public IBinder asBinder() { 1584 return this; 1585 } 1586 1587 @Override 1588 public void onDeviceError(final int errorCode, CaptureResultExtras resultExtras) { 1589 if (DEBUG) { 1590 Log.d(TAG, String.format( 1591 "Device error received, code %d, frame number %d, request ID %d, subseq ID %d", 1592 errorCode, resultExtras.getFrameNumber(), resultExtras.getRequestId(), 1593 resultExtras.getSubsequenceId())); 1594 } 1595 1596 synchronized(mInterfaceLock) { 1597 if (mRemoteDevice == null) { 1598 return; // Camera already closed 1599 } 1600 1601 switch (errorCode) { 1602 case ERROR_CAMERA_DISCONNECTED: 1603 CameraDeviceImpl.this.mDeviceHandler.post(mCallOnDisconnected); 1604 break; 1605 default: 1606 Log.e(TAG, "Unknown error from camera device: " + errorCode); 1607 // no break 1608 case ERROR_CAMERA_DEVICE: 1609 case ERROR_CAMERA_SERVICE: 1610 mInError = true; 1611 Runnable r = new Runnable() { 1612 @Override 1613 public void run() { 1614 if (!CameraDeviceImpl.this.isClosed()) { 1615 mDeviceCallback.onError(CameraDeviceImpl.this, errorCode); 1616 } 1617 } 1618 }; 1619 CameraDeviceImpl.this.mDeviceHandler.post(r); 1620 break; 1621 case ERROR_CAMERA_REQUEST: 1622 case ERROR_CAMERA_RESULT: 1623 case ERROR_CAMERA_BUFFER: 1624 onCaptureErrorLocked(errorCode, resultExtras); 1625 break; 1626 } 1627 } 1628 } 1629 1630 @Override 1631 public void onDeviceIdle() { 1632 if (DEBUG) { 1633 Log.d(TAG, "Camera now idle"); 1634 } 1635 synchronized(mInterfaceLock) { 1636 if (mRemoteDevice == null) return; // Camera already closed 1637 1638 if (!CameraDeviceImpl.this.mIdle) { 1639 CameraDeviceImpl.this.mDeviceHandler.post(mCallOnIdle); 1640 } 1641 CameraDeviceImpl.this.mIdle = true; 1642 } 1643 } 1644 1645 @Override 1646 public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) { 1647 int requestId = resultExtras.getRequestId(); 1648 final long frameNumber = resultExtras.getFrameNumber(); 1649 1650 if (DEBUG) { 1651 Log.d(TAG, "Capture started for id " + requestId + " frame number " + frameNumber); 1652 } 1653 final CaptureCallbackHolder holder; 1654 1655 synchronized(mInterfaceLock) { 1656 if (mRemoteDevice == null) return; // Camera already closed 1657 1658 // Get the callback for this frame ID, if there is one 1659 holder = CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId); 1660 1661 if (holder == null) { 1662 return; 1663 } 1664 1665 if (isClosed()) return; 1666 1667 // Dispatch capture start notice 1668 holder.getHandler().post( 1669 new Runnable() { 1670 @Override 1671 public void run() { 1672 if (!CameraDeviceImpl.this.isClosed()) { 1673 holder.getCallback().onCaptureStarted( 1674 CameraDeviceImpl.this, 1675 holder.getRequest(resultExtras.getSubsequenceId()), 1676 timestamp, frameNumber); 1677 } 1678 } 1679 }); 1680 1681 } 1682 } 1683 1684 @Override 1685 public void onResultReceived(CameraMetadataNative result, 1686 CaptureResultExtras resultExtras) throws RemoteException { 1687 1688 int requestId = resultExtras.getRequestId(); 1689 long frameNumber = resultExtras.getFrameNumber(); 1690 1691 if (DEBUG) { 1692 Log.v(TAG, "Received result frame " + frameNumber + " for id " 1693 + requestId); 1694 } 1695 1696 synchronized(mInterfaceLock) { 1697 if (mRemoteDevice == null) return; // Camera already closed 1698 1699 // TODO: Handle CameraCharacteristics access from CaptureResult correctly. 1700 result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE, 1701 getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE)); 1702 1703 final CaptureCallbackHolder holder = 1704 CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId); 1705 final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId()); 1706 1707 boolean isPartialResult = 1708 (resultExtras.getPartialResultCount() < mTotalPartialCount); 1709 boolean isReprocess = request.isReprocess(); 1710 1711 // Check if we have a callback for this 1712 if (holder == null) { 1713 if (DEBUG) { 1714 Log.d(TAG, 1715 "holder is null, early return at frame " 1716 + frameNumber); 1717 } 1718 1719 mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult, 1720 isReprocess); 1721 1722 return; 1723 } 1724 1725 if (isClosed()) { 1726 if (DEBUG) { 1727 Log.d(TAG, 1728 "camera is closed, early return at frame " 1729 + frameNumber); 1730 } 1731 1732 mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult, 1733 isReprocess); 1734 return; 1735 } 1736 1737 1738 Runnable resultDispatch = null; 1739 1740 CaptureResult finalResult; 1741 1742 // Either send a partial result or the final capture completed result 1743 if (isPartialResult) { 1744 final CaptureResult resultAsCapture = 1745 new CaptureResult(result, request, resultExtras); 1746 1747 // Partial result 1748 resultDispatch = new Runnable() { 1749 @Override 1750 public void run() { 1751 if (!CameraDeviceImpl.this.isClosed()){ 1752 holder.getCallback().onCaptureProgressed( 1753 CameraDeviceImpl.this, 1754 request, 1755 resultAsCapture); 1756 } 1757 } 1758 }; 1759 1760 finalResult = resultAsCapture; 1761 } else { 1762 List<CaptureResult> partialResults = 1763 mFrameNumberTracker.popPartialResults(frameNumber); 1764 1765 final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result, 1766 request, resultExtras, partialResults, holder.getSessionId()); 1767 1768 // Final capture result 1769 resultDispatch = new Runnable() { 1770 @Override 1771 public void run() { 1772 if (!CameraDeviceImpl.this.isClosed()){ 1773 holder.getCallback().onCaptureCompleted( 1774 CameraDeviceImpl.this, 1775 request, 1776 resultAsCapture); 1777 } 1778 } 1779 }; 1780 1781 finalResult = resultAsCapture; 1782 } 1783 1784 holder.getHandler().post(resultDispatch); 1785 1786 // Collect the partials for a total result; or mark the frame as totally completed 1787 mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult, 1788 isReprocess); 1789 1790 // Fire onCaptureSequenceCompleted 1791 if (!isPartialResult) { 1792 checkAndFireSequenceComplete(); 1793 } 1794 } 1795 } 1796 1797 @Override 1798 public void onPrepared(int streamId) { 1799 final OutputConfiguration output; 1800 final StateCallbackKK sessionCallback; 1801 1802 if (DEBUG) { 1803 Log.v(TAG, "Stream " + streamId + " is prepared"); 1804 } 1805 1806 synchronized(mInterfaceLock) { 1807 output = mConfiguredOutputs.get(streamId); 1808 sessionCallback = mSessionStateCallback; 1809 } 1810 1811 if (sessionCallback == null) return; 1812 1813 if (output == null) { 1814 Log.w(TAG, "onPrepared invoked for unknown output Surface"); 1815 return; 1816 } 1817 final Surface surface = output.getSurface(); 1818 1819 sessionCallback.onSurfacePrepared(surface); 1820 } 1821 1822 /** 1823 * Called by onDeviceError for handling single-capture failures. 1824 */ 1825 private void onCaptureErrorLocked(int errorCode, CaptureResultExtras resultExtras) { 1826 1827 final int requestId = resultExtras.getRequestId(); 1828 final int subsequenceId = resultExtras.getSubsequenceId(); 1829 final long frameNumber = resultExtras.getFrameNumber(); 1830 final CaptureCallbackHolder holder = 1831 CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId); 1832 1833 final CaptureRequest request = holder.getRequest(subsequenceId); 1834 1835 // No way to report buffer errors right now 1836 if (errorCode == ERROR_CAMERA_BUFFER) { 1837 Log.e(TAG, String.format("Lost output buffer reported for frame %d", frameNumber)); 1838 return; 1839 } 1840 1841 boolean mayHaveBuffers = (errorCode == ERROR_CAMERA_RESULT); 1842 1843 // This is only approximate - exact handling needs the camera service and HAL to 1844 // disambiguate between request failures to due abort and due to real errors. 1845 // For now, assume that if the session believes we're mid-abort, then the error 1846 // is due to abort. 1847 int reason = (mCurrentSession != null && mCurrentSession.isAborting()) ? 1848 CaptureFailure.REASON_FLUSHED : 1849 CaptureFailure.REASON_ERROR; 1850 1851 final CaptureFailure failure = new CaptureFailure( 1852 request, 1853 reason, 1854 /*dropped*/ mayHaveBuffers, 1855 requestId, 1856 frameNumber); 1857 1858 Runnable failureDispatch = new Runnable() { 1859 @Override 1860 public void run() { 1861 if (!CameraDeviceImpl.this.isClosed()){ 1862 holder.getCallback().onCaptureFailed( 1863 CameraDeviceImpl.this, 1864 request, 1865 failure); 1866 } 1867 } 1868 }; 1869 holder.getHandler().post(failureDispatch); 1870 1871 // Fire onCaptureSequenceCompleted if appropriate 1872 if (DEBUG) { 1873 Log.v(TAG, String.format("got error frame %d", frameNumber)); 1874 } 1875 mFrameNumberTracker.updateTracker(frameNumber, /*error*/true, request.isReprocess()); 1876 checkAndFireSequenceComplete(); 1877 } 1878 1879 } // public class CameraDeviceCallbacks 1880 1881 /** 1882 * Default handler management. 1883 * 1884 * <p> 1885 * If handler is null, get the current thread's 1886 * Looper to create a Handler with. If no looper exists, throw {@code IllegalArgumentException}. 1887 * </p> 1888 */ 1889 static Handler checkHandler(Handler handler) { 1890 if (handler == null) { 1891 Looper looper = Looper.myLooper(); 1892 if (looper == null) { 1893 throw new IllegalArgumentException( 1894 "No handler given, and current thread has no looper!"); 1895 } 1896 handler = new Handler(looper); 1897 } 1898 return handler; 1899 } 1900 1901 /** 1902 * Default handler management, conditional on there being a callback. 1903 * 1904 * <p>If the callback isn't null, check the handler, otherwise pass it through.</p> 1905 */ 1906 static <T> Handler checkHandler(Handler handler, T callback) { 1907 if (callback != null) { 1908 return checkHandler(handler); 1909 } 1910 return handler; 1911 } 1912 1913 private void checkIfCameraClosedOrInError() throws CameraAccessException { 1914 if (mRemoteDevice == null) { 1915 throw new IllegalStateException("CameraDevice was already closed"); 1916 } 1917 if (mInError) { 1918 throw new CameraAccessException(CameraAccessException.CAMERA_ERROR, 1919 "The camera device has encountered a serious error"); 1920 } 1921 } 1922 1923 /** Whether the camera device has started to close (may not yet have finished) */ 1924 private boolean isClosed() { 1925 return mClosing.get(); 1926 } 1927 1928 private CameraCharacteristics getCharacteristics() { 1929 return mCharacteristics; 1930 } 1931 1932 private void checkConstrainedHighSpeedSurfaces(Collection<Surface> surfaces, 1933 Range<Integer> fpsRange) { 1934 if (surfaces == null || surfaces.size() == 0 || surfaces.size() > 2) { 1935 throw new IllegalArgumentException("Output target surface list must not be null and" 1936 + " the size must be 1 or 2"); 1937 } 1938 1939 StreamConfigurationMap config = 1940 getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 1941 List<Size> highSpeedSizes = null; 1942 if (fpsRange == null) { 1943 highSpeedSizes = Arrays.asList(config.getHighSpeedVideoSizes()); 1944 } else { 1945 // Check the FPS range first if provided 1946 Range<Integer>[] highSpeedFpsRanges = config.getHighSpeedVideoFpsRanges(); 1947 if(!Arrays.asList(highSpeedFpsRanges).contains(fpsRange)) { 1948 throw new IllegalArgumentException("Fps range " + fpsRange.toString() + " in the" 1949 + " request is not a supported high speed fps range " + 1950 Arrays.toString(highSpeedFpsRanges)); 1951 } 1952 highSpeedSizes = Arrays.asList(config.getHighSpeedVideoSizesFor(fpsRange)); 1953 } 1954 1955 for (Surface surface : surfaces) { 1956 // Surface size must be supported high speed sizes. 1957 Size surfaceSize = SurfaceUtils.getSurfaceSize(surface); 1958 int surfaceFormat = SurfaceUtils.getSurfaceFormat(surface); 1959 1960 if (surfaceFormat != ImageFormat.PRIVATE) { 1961 throw new IllegalArgumentException("Surface format is not for preview or" 1962 + " hardware video encoding" + surfaceFormat); 1963 } 1964 1965 if (!highSpeedSizes.contains(surfaceSize)) { 1966 throw new IllegalArgumentException("Surface size " + surfaceSize.toString() + " is" 1967 + " not part of the high speed supported size list " + 1968 Arrays.toString(highSpeedSizes.toArray())); 1969 } 1970 // Each output surface must be either preview surface or recording surface. 1971 if (!SurfaceUtils.isSurfaceForPreview(surface) && 1972 !SurfaceUtils.isSurfaceForHwVideoEncoder(surface)) { 1973 throw new IllegalArgumentException("This output surface is neither preview nor " 1974 + "hardware video encoding surface"); 1975 } 1976 if (SurfaceUtils.isSurfaceForPreview(surface) && 1977 SurfaceUtils.isSurfaceForHwVideoEncoder(surface)) { 1978 throw new IllegalArgumentException("This output surface can not be both preview" 1979 + " and hardware video encoding surface"); 1980 } 1981 } 1982 1983 // For 2 output surface case, they shouldn't be same type. 1984 if (surfaces.size() == 2) { 1985 // Up to here, each surface can only be either preview or recording. 1986 Iterator<Surface> iterator = surfaces.iterator(); 1987 boolean isFirstSurfacePreview = 1988 SurfaceUtils.isSurfaceForPreview(iterator.next()); 1989 boolean isSecondSurfacePreview = 1990 SurfaceUtils.isSurfaceForPreview(iterator.next()); 1991 if (isFirstSurfacePreview == isSecondSurfacePreview) { 1992 throw new IllegalArgumentException("The 2 output surfaces must have different" 1993 + " type"); 1994 } 1995 } 1996 } 1997 1998 @Override 1999 public void createConstrainedHighSpeedCaptureSession(List<Surface> outputs, 2000 android.hardware.camera2.CameraCaptureSession.StateCallback callback, Handler handler) 2001 throws CameraAccessException { 2002 if (outputs == null || outputs.size() == 0 || outputs.size() > 2) { 2003 throw new IllegalArgumentException( 2004 "Output surface list must not be null and the size must be no more than 2"); 2005 } 2006 checkConstrainedHighSpeedSurfaces(outputs, /*fpsRange*/null); 2007 2008 List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size()); 2009 for (Surface surface : outputs) { 2010 outConfigurations.add(new OutputConfiguration(surface)); 2011 } 2012 createCaptureSessionInternal(null, outConfigurations, callback, handler, 2013 /*isConstrainedHighSpeed*/true); 2014 } 2015 2016 @Override 2017 public List<CaptureRequest> createConstrainedHighSpeedRequestList(CaptureRequest request) 2018 throws CameraAccessException { 2019 if (request == null) { 2020 throw new IllegalArgumentException("Input capture request must not be null"); 2021 } 2022 Collection<Surface> outputSurfaces = request.getTargets(); 2023 Range<Integer> fpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE); 2024 checkConstrainedHighSpeedSurfaces(outputSurfaces, fpsRange); 2025 2026 // Request list size: to limit the preview to 30fps, need use maxFps/30; to maximize 2027 // the preview frame rate, should use maxBatch size for that high speed stream 2028 // configuration. We choose the former for now. 2029 int requestListSize = fpsRange.getUpper() / 30; 2030 List<CaptureRequest> requestList = new ArrayList<CaptureRequest>(); 2031 2032 // Prepare the Request builders: need carry over the request controls. 2033 // First, create a request builder that will only include preview or recording target. 2034 CameraMetadataNative requestMetadata = new CameraMetadataNative(request.getNativeCopy()); 2035 CaptureRequest.Builder singleTargetRequestBuilder = new CaptureRequest.Builder( 2036 requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE); 2037 2038 // Overwrite the capture intent to make sure a good value is set. 2039 Surface[] surfaces = (Surface[])outputSurfaces.toArray(); 2040 if (outputSurfaces.size() == 1 && SurfaceUtils.isSurfaceForHwVideoEncoder(surfaces[0])) { 2041 singleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, 2042 CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW); 2043 } else { 2044 // Video only, or preview + video 2045 singleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, 2046 CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD); 2047 } 2048 singleTargetRequestBuilder.setPartOfCHSRequestList(/*partOfCHSList*/true); 2049 2050 // Second, Create a request builder that will include both preview and recording targets. 2051 CaptureRequest.Builder doubleTargetRequestBuilder = null; 2052 if (outputSurfaces.size() == 2) { 2053 doubleTargetRequestBuilder = new CaptureRequest.Builder( 2054 requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE); 2055 doubleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, 2056 CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD); 2057 doubleTargetRequestBuilder.addTarget(surfaces[0]); 2058 doubleTargetRequestBuilder.addTarget(surfaces[1]); 2059 doubleTargetRequestBuilder.setPartOfCHSRequestList(/*partOfCHSList*/true); 2060 // Make sure singleTargetRequestBuilder contains only recording surface for 2061 // preview + recording case. 2062 Surface recordingSurface = surfaces[0]; 2063 if (!SurfaceUtils.isSurfaceForHwVideoEncoder(recordingSurface)) { 2064 recordingSurface = surfaces[1]; 2065 } 2066 singleTargetRequestBuilder.addTarget(recordingSurface); 2067 } else { 2068 // Single output case: either recording or preview. 2069 singleTargetRequestBuilder.addTarget(surfaces[0]); 2070 } 2071 2072 // Generate the final request list. 2073 for (int i = 0; i < requestListSize; i++) { 2074 if (i == 0 && doubleTargetRequestBuilder != null) { 2075 // First request should be recording + preview request 2076 requestList.add(doubleTargetRequestBuilder.build()); 2077 } else { 2078 requestList.add(singleTargetRequestBuilder.build()); 2079 } 2080 } 2081 2082 return Collections.unmodifiableList(requestList); 2083 } 2084} 2085