2 * Copyright (C) 2013 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.hardware.camera2.impl;
19 import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE;
21 import android.graphics.ImageFormat;
22 import android.hardware.camera2.CameraAccessException;
23 import android.hardware.camera2.CameraCaptureSession;
24 import android.hardware.camera2.CameraCharacteristics;
25 import android.hardware.camera2.CameraDevice;
26 import android.hardware.camera2.CaptureRequest;
27 import android.hardware.camera2.CaptureResult;
28 import android.hardware.camera2.CaptureFailure;
29 import android.hardware.camera2.ICameraDeviceCallbacks;
30 import android.hardware.camera2.ICameraDeviceUser;
31 import android.hardware.camera2.TotalCaptureResult;
32 import android.hardware.camera2.params.InputConfiguration;
33 import android.hardware.camera2.params.OutputConfiguration;
34 import android.hardware.camera2.params.ReprocessFormatsMap;
35 import android.hardware.camera2.params.StreamConfigurationMap;
36 import android.hardware.camera2.utils.CameraBinderDecorator;
37 import android.hardware.camera2.utils.CameraRuntimeException;
38 import android.hardware.camera2.utils.LongParcelable;
39 import android.hardware.camera2.utils.SurfaceUtils;
40 import android.os.Handler;
41 import android.os.IBinder;
42 import android.os.Looper;
43 import android.os.RemoteException;
44 import android.util.Log;
45 import android.util.Range;
46 import android.util.Size;
47 import android.util.SparseArray;
48 import android.view.Surface;
50 import java.util.AbstractMap.SimpleEntry;
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.Collection;
54 import java.util.Collections;
55 import java.util.concurrent.atomic.AtomicBoolean;
56 import java.util.HashMap;
57 import java.util.HashSet;
58 import java.util.Iterator;
59 import java.util.List;
60 import java.util.LinkedList;
61 import java.util.TreeMap;
64 * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate
66 public class CameraDeviceImpl extends CameraDevice {
67 private final String TAG;
68 private final boolean DEBUG = false;
70 private static final int REQUEST_ID_NONE = -1;
72 // TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
73 private ICameraDeviceUser mRemoteDevice;
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();
79 private final StateCallback mDeviceCallback;
80 private volatile StateCallbackKK mSessionStateCallback;
81 private final Handler mDeviceHandler;
83 private final AtomicBoolean mClosing = new AtomicBoolean();
84 private boolean mInError = false;
85 private boolean mIdle = true;
87 /** map request IDs to callback/request data */
88 private final SparseArray<CaptureCallbackHolder> mCaptureCallbackMap =
89 new SparseArray<CaptureCallbackHolder>();
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 =
98 private final SparseArray<Size> mConfiguredOutputSizes =
101 private final String mCameraId;
102 private final CameraCharacteristics mCharacteristics;
103 private final int mTotalPartialCount;
106 * A list tracking request and its expected last regular frame number and last reprocess frame
107 * number. Updated when calling ICameraDeviceUser methods.
109 private final List<RequestLastFrameNumbersHolder> mRequestLastFrameNumbersList =
113 * An object tracking received frame numbers.
114 * Updated when receiving callbacks from ICameraDeviceCallbacks.
116 private final FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker();
118 private CameraCaptureSessionCore mCurrentSession;
119 private int mNextSessionId = 0;
121 // Runnables for all state transitions, except error, which needs the
122 // error code argument
124 private final Runnable mCallOnOpened = new Runnable() {
127 StateCallbackKK sessionCallback = null;
128 synchronized(mInterfaceLock) {
129 if (mRemoteDevice == null) return; // Camera already closed
131 sessionCallback = mSessionStateCallback;
133 if (sessionCallback != null) {
134 sessionCallback.onOpened(CameraDeviceImpl.this);
136 mDeviceCallback.onOpened(CameraDeviceImpl.this);
140 private final Runnable mCallOnUnconfigured = new Runnable() {
143 StateCallbackKK sessionCallback = null;
144 synchronized(mInterfaceLock) {
145 if (mRemoteDevice == null) return; // Camera already closed
147 sessionCallback = mSessionStateCallback;
149 if (sessionCallback != null) {
150 sessionCallback.onUnconfigured(CameraDeviceImpl.this);
155 private final Runnable mCallOnActive = new Runnable() {
158 StateCallbackKK sessionCallback = null;
159 synchronized(mInterfaceLock) {
160 if (mRemoteDevice == null) return; // Camera already closed
162 sessionCallback = mSessionStateCallback;
164 if (sessionCallback != null) {
165 sessionCallback.onActive(CameraDeviceImpl.this);
170 private final Runnable mCallOnBusy = new Runnable() {
173 StateCallbackKK sessionCallback = null;
174 synchronized(mInterfaceLock) {
175 if (mRemoteDevice == null) return; // Camera already closed
177 sessionCallback = mSessionStateCallback;
179 if (sessionCallback != null) {
180 sessionCallback.onBusy(CameraDeviceImpl.this);
185 private final Runnable mCallOnClosed = new Runnable() {
186 private boolean mClosedOnce = false;
191 throw new AssertionError("Don't post #onClosed more than once");
193 StateCallbackKK sessionCallback = null;
194 synchronized(mInterfaceLock) {
195 sessionCallback = mSessionStateCallback;
197 if (sessionCallback != null) {
198 sessionCallback.onClosed(CameraDeviceImpl.this);
200 mDeviceCallback.onClosed(CameraDeviceImpl.this);
205 private final Runnable mCallOnIdle = new Runnable() {
208 StateCallbackKK sessionCallback = null;
209 synchronized(mInterfaceLock) {
210 if (mRemoteDevice == null) return; // Camera already closed
212 sessionCallback = mSessionStateCallback;
214 if (sessionCallback != null) {
215 sessionCallback.onIdle(CameraDeviceImpl.this);
220 private final Runnable mCallOnDisconnected = new Runnable() {
223 StateCallbackKK sessionCallback = null;
224 synchronized(mInterfaceLock) {
225 if (mRemoteDevice == null) return; // Camera already closed
227 sessionCallback = mSessionStateCallback;
229 if (sessionCallback != null) {
230 sessionCallback.onDisconnected(CameraDeviceImpl.this);
232 mDeviceCallback.onDisconnected(CameraDeviceImpl.this);
236 public CameraDeviceImpl(String cameraId, StateCallback callback, Handler handler,
237 CameraCharacteristics characteristics) {
238 if (cameraId == null || callback == null || handler == null || characteristics == null) {
239 throw new IllegalArgumentException("Null argument given");
241 mCameraId = cameraId;
242 mDeviceCallback = callback;
243 mDeviceHandler = handler;
244 mCharacteristics = characteristics;
246 final int MAX_TAG_LEN = 23;
247 String tag = String.format("CameraDevice-JV-%s", mCameraId);
248 if (tag.length() > MAX_TAG_LEN) {
249 tag = tag.substring(0, MAX_TAG_LEN);
253 Integer partialCount =
254 mCharacteristics.get(CameraCharacteristics.REQUEST_PARTIAL_RESULT_COUNT);
255 if (partialCount == null) {
256 // 1 means partial result is not supported.
257 mTotalPartialCount = 1;
259 mTotalPartialCount = partialCount;
263 public CameraDeviceCallbacks getCallbacks() {
267 public void setRemoteDevice(ICameraDeviceUser remoteDevice) {
268 synchronized(mInterfaceLock) {
269 // TODO: Move from decorator to direct binder-mediated exceptions
270 // If setRemoteFailure already called, do nothing
271 if (mInError) return;
273 mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice);
275 mDeviceHandler.post(mCallOnOpened);
276 mDeviceHandler.post(mCallOnUnconfigured);
281 * Call to indicate failed connection to a remote camera device.
283 * <p>This places the camera device in the error state and informs the callback.
284 * Use in place of setRemoteDevice() when startup fails.</p>
286 public void setRemoteFailure(final CameraRuntimeException failure) {
287 int failureCode = StateCallback.ERROR_CAMERA_DEVICE;
288 boolean failureIsError = true;
290 switch (failure.getReason()) {
291 case CameraAccessException.CAMERA_IN_USE:
292 failureCode = StateCallback.ERROR_CAMERA_IN_USE;
294 case CameraAccessException.MAX_CAMERAS_IN_USE:
295 failureCode = StateCallback.ERROR_MAX_CAMERAS_IN_USE;
297 case CameraAccessException.CAMERA_DISABLED:
298 failureCode = StateCallback.ERROR_CAMERA_DISABLED;
300 case CameraAccessException.CAMERA_DISCONNECTED:
301 failureIsError = false;
303 case CameraAccessException.CAMERA_ERROR:
304 failureCode = StateCallback.ERROR_CAMERA_DEVICE;
307 Log.wtf(TAG, "Unknown failure in opening camera device: " + failure.getReason());
310 final int code = failureCode;
311 final boolean isError = failureIsError;
312 synchronized(mInterfaceLock) {
314 mDeviceHandler.post(new Runnable() {
318 mDeviceCallback.onError(CameraDeviceImpl.this, code);
320 mDeviceCallback.onDisconnected(CameraDeviceImpl.this);
328 public String getId() {
332 public void configureOutputs(List<Surface> outputs) throws CameraAccessException {
333 // Leave this here for backwards compatibility with older code using this directly
334 ArrayList<OutputConfiguration> outputConfigs = new ArrayList<>(outputs.size());
335 for (Surface s : outputs) {
336 outputConfigs.add(new OutputConfiguration(s));
338 configureStreamsChecked(/*inputConfig*/null, outputConfigs,
339 /*isConstrainedHighSpeed*/false);
344 * Attempt to configure the input and outputs; the device goes to idle and then configures the
345 * new input and outputs if possible.
347 * <p>The configuration may gracefully fail, if input configuration is not supported,
348 * if there are too many outputs, if the formats are not supported, or if the sizes for that
349 * format is not supported. In this case this function will return {@code false} and the
350 * unconfigured callback will be fired.</p>
352 * <p>If the configuration succeeds (with 1 or more outputs with or without an input),
353 * then the idle callback is fired. Unconfiguring the device always fires the idle callback.</p>
355 * @param inputConfig input configuration or {@code null} for no input
356 * @param outputs a list of one or more surfaces, or {@code null} to unconfigure
357 * @param isConstrainedHighSpeed If the streams configuration is for constrained high speed output.
358 * @return whether or not the configuration was successful
360 * @throws CameraAccessException if there were any unexpected problems during configuration
362 public boolean configureStreamsChecked(InputConfiguration inputConfig,
363 List<OutputConfiguration> outputs, boolean isConstrainedHighSpeed)
364 throws CameraAccessException {
365 // Treat a null input the same an empty list
366 if (outputs == null) {
367 outputs = new ArrayList<OutputConfiguration>();
369 if (outputs.size() == 0 && inputConfig != null) {
370 throw new IllegalArgumentException("cannot configure an input stream without " +
371 "any output streams");
374 checkInputConfiguration(inputConfig);
376 boolean success = false;
378 synchronized(mInterfaceLock) {
379 checkIfCameraClosedOrInError();
381 HashSet<OutputConfiguration> addSet = new HashSet<OutputConfiguration>(outputs);
383 List<Integer> deleteList = new ArrayList<Integer>();
385 // Determine which streams need to be created, which to be deleted
386 for (int i = 0; i < mConfiguredOutputs.size(); ++i) {
387 int streamId = mConfiguredOutputs.keyAt(i);
388 OutputConfiguration outConfig = mConfiguredOutputs.valueAt(i);
390 if (!outputs.contains(outConfig)) {
391 deleteList.add(streamId);
393 // Even if same surface and rotation, the surface can have re-sized.
394 // If so, we must create a new stream to ensure HAL is configured correctly.
395 Size outSize = SurfaceUtils.getSurfaceSize(outConfig.getSurface());
396 if (!outSize.equals(mConfiguredOutputSizes.valueAt(i))) {
397 deleteList.add(streamId);
399 addSet.remove(outConfig); // Don't create a stream previously created
404 mDeviceHandler.post(mCallOnBusy);
410 mRemoteDevice.beginConfigure();
412 // reconfigure the input stream if the input configuration is different.
413 InputConfiguration currentInputConfig = mConfiguredInput.getValue();
414 if (inputConfig != currentInputConfig &&
415 (inputConfig == null || !inputConfig.equals(currentInputConfig))) {
416 if (currentInputConfig != null) {
417 mRemoteDevice.deleteStream(mConfiguredInput.getKey());
418 mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>(
419 REQUEST_ID_NONE, null);
421 if (inputConfig != null) {
422 int streamId = mRemoteDevice.createInputStream(inputConfig.getWidth(),
423 inputConfig.getHeight(), inputConfig.getFormat());
424 mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>(
425 streamId, inputConfig);
429 // Delete all streams first (to free up HW resources)
430 for (Integer streamId : deleteList) {
431 mRemoteDevice.deleteStream(streamId);
432 mConfiguredOutputs.delete(streamId);
433 mConfiguredOutputSizes.delete(streamId);
436 // Add all new streams
437 for (OutputConfiguration outConfig : outputs) {
438 if (addSet.contains(outConfig)) {
439 int streamId = mRemoteDevice.createStream(outConfig);
440 Size outSize = SurfaceUtils.getSurfaceSize(outConfig.getSurface());
441 mConfiguredOutputs.put(streamId, outConfig);
442 mConfiguredOutputSizes.put(streamId, outSize);
447 mRemoteDevice.endConfigure(isConstrainedHighSpeed);
449 catch (IllegalArgumentException e) {
450 // OK. camera service can reject stream config if it's not supported by HAL
451 // This is only the result of a programmer misusing the camera2 api.
452 Log.w(TAG, "Stream configuration failed");
457 } catch (CameraRuntimeException e) {
458 if (e.getReason() == CAMERA_IN_USE) {
459 throw new IllegalStateException("The camera is currently busy." +
460 " You must wait until the previous operation completes.");
464 } catch (RemoteException e) {
468 if (success && outputs.size() > 0) {
469 mDeviceHandler.post(mCallOnIdle);
471 // Always return to the 'unconfigured' state if we didn't hit a fatal error
472 mDeviceHandler.post(mCallOnUnconfigured);
481 public void createCaptureSession(List<Surface> outputs,
482 CameraCaptureSession.StateCallback callback, Handler handler)
483 throws CameraAccessException {
484 List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size());
485 for (Surface surface : outputs) {
486 outConfigurations.add(new OutputConfiguration(surface));
488 createCaptureSessionInternal(null, outConfigurations, callback, handler,
489 /*isConstrainedHighSpeed*/false);
493 public void createCaptureSessionByOutputConfiguration(
494 List<OutputConfiguration> outputConfigurations,
495 CameraCaptureSession.StateCallback callback, Handler handler)
496 throws CameraAccessException {
498 Log.d(TAG, "createCaptureSessionByOutputConfiguration");
501 createCaptureSessionInternal(null, outputConfigurations, callback, handler,
502 /*isConstrainedHighSpeed*/false);
506 public void createReprocessableCaptureSession(InputConfiguration inputConfig,
507 List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler)
508 throws CameraAccessException {
510 Log.d(TAG, "createReprocessableCaptureSession");
513 if (inputConfig == null) {
514 throw new IllegalArgumentException("inputConfig cannot be null when creating a " +
515 "reprocessable capture session");
517 List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size());
518 for (Surface surface : outputs) {
519 outConfigurations.add(new OutputConfiguration(surface));
521 createCaptureSessionInternal(inputConfig, outConfigurations, callback, handler,
522 /*isConstrainedHighSpeed*/false);
526 public void createConstrainedHighSpeedCaptureSession(List<Surface> outputs,
527 android.hardware.camera2.CameraCaptureSession.StateCallback callback, Handler handler)
528 throws CameraAccessException {
529 if (outputs == null || outputs.size() == 0 || outputs.size() > 2) {
530 throw new IllegalArgumentException(
531 "Output surface list must not be null and the size must be no more than 2");
533 StreamConfigurationMap config =
534 getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
535 SurfaceUtils.checkConstrainedHighSpeedSurfaces(outputs, /*fpsRange*/null, config);
537 List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size());
538 for (Surface surface : outputs) {
539 outConfigurations.add(new OutputConfiguration(surface));
541 createCaptureSessionInternal(null, outConfigurations, callback, handler,
542 /*isConstrainedHighSpeed*/true);
545 private void createCaptureSessionInternal(InputConfiguration inputConfig,
546 List<OutputConfiguration> outputConfigurations,
547 CameraCaptureSession.StateCallback callback, Handler handler,
548 boolean isConstrainedHighSpeed) throws CameraAccessException {
549 synchronized(mInterfaceLock) {
551 Log.d(TAG, "createCaptureSessionInternal");
554 checkIfCameraClosedOrInError();
556 if (isConstrainedHighSpeed && inputConfig != null) {
557 throw new IllegalArgumentException("Constrained high speed session doesn't support"
558 + " input configuration yet.");
561 // Notify current session that it's going away, before starting camera operations
562 // After this call completes, the session is not allowed to call into CameraDeviceImpl
563 if (mCurrentSession != null) {
564 mCurrentSession.replaceSessionClose();
567 // TODO: dont block for this
568 boolean configureSuccess = true;
569 CameraAccessException pendingException = null;
570 Surface input = null;
572 // configure streams and then block until IDLE
573 configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations,
574 isConstrainedHighSpeed);
575 if (configureSuccess == true && inputConfig != null) {
576 input = new Surface();
578 mRemoteDevice.getInputSurface(/*out*/input);
579 } catch (CameraRuntimeException e) {
583 } catch (CameraAccessException e) {
584 configureSuccess = false;
585 pendingException = e;
588 Log.v(TAG, "createCaptureSession - failed with exception ", e);
590 } catch (RemoteException e) {
595 List<Surface> outSurfaces = new ArrayList<>(outputConfigurations.size());
596 for (OutputConfiguration config : outputConfigurations) {
597 outSurfaces.add(config.getSurface());
599 // Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise.
600 CameraCaptureSessionCore newSession = null;
601 if (isConstrainedHighSpeed) {
602 newSession = new CameraConstrainedHighSpeedCaptureSessionImpl(mNextSessionId++,
603 outSurfaces, callback, handler, this, mDeviceHandler, configureSuccess,
606 newSession = new CameraCaptureSessionImpl(mNextSessionId++, input,
607 outSurfaces, callback, handler, this, mDeviceHandler,
611 // TODO: wait until current session closes, then create the new session
612 mCurrentSession = newSession;
614 if (pendingException != null) {
615 throw pendingException;
618 mSessionStateCallback = mCurrentSession.getDeviceStateCallback();
623 * For use by backwards-compatibility code only.
625 public void setSessionListener(StateCallbackKK sessionCallback) {
626 synchronized(mInterfaceLock) {
627 mSessionStateCallback = sessionCallback;
632 public CaptureRequest.Builder createCaptureRequest(int templateType)
633 throws CameraAccessException {
634 synchronized(mInterfaceLock) {
635 checkIfCameraClosedOrInError();
637 CameraMetadataNative templatedRequest = new CameraMetadataNative();
640 mRemoteDevice.createDefaultRequest(templateType, /*out*/templatedRequest);
641 } catch (CameraRuntimeException e) {
643 } catch (RemoteException e) {
648 CaptureRequest.Builder builder = new CaptureRequest.Builder(
649 templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE);
656 public CaptureRequest.Builder createReprocessCaptureRequest(TotalCaptureResult inputResult)
657 throws CameraAccessException {
658 synchronized(mInterfaceLock) {
659 checkIfCameraClosedOrInError();
661 CameraMetadataNative resultMetadata = new
662 CameraMetadataNative(inputResult.getNativeCopy());
664 return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true,
665 inputResult.getSessionId());
669 public void prepare(Surface surface) throws CameraAccessException {
670 if (surface == null) throw new IllegalArgumentException("Surface is null");
672 synchronized(mInterfaceLock) {
674 for (int i = 0; i < mConfiguredOutputs.size(); i++) {
675 if (surface == mConfiguredOutputs.valueAt(i).getSurface()) {
676 streamId = mConfiguredOutputs.keyAt(i);
680 if (streamId == -1) {
681 throw new IllegalArgumentException("Surface is not part of this session");
684 mRemoteDevice.prepare(streamId);
685 } catch (CameraRuntimeException e) {
687 } catch (RemoteException e) {
694 public void prepare(int maxCount, Surface surface) throws CameraAccessException {
695 if (surface == null) throw new IllegalArgumentException("Surface is null");
696 if (maxCount <= 0) throw new IllegalArgumentException("Invalid maxCount given: " +
699 synchronized(mInterfaceLock) {
701 for (int i = 0; i < mConfiguredOutputs.size(); i++) {
702 if (surface == mConfiguredOutputs.valueAt(i).getSurface()) {
703 streamId = mConfiguredOutputs.keyAt(i);
707 if (streamId == -1) {
708 throw new IllegalArgumentException("Surface is not part of this session");
711 mRemoteDevice.prepare2(maxCount, streamId);
712 } catch (CameraRuntimeException e) {
714 } catch (RemoteException e) {
721 public void tearDown(Surface surface) throws CameraAccessException {
722 if (surface == null) throw new IllegalArgumentException("Surface is null");
724 synchronized(mInterfaceLock) {
726 for (int i = 0; i < mConfiguredOutputs.size(); i++) {
727 if (surface == mConfiguredOutputs.valueAt(i).getSurface()) {
728 streamId = mConfiguredOutputs.keyAt(i);
732 if (streamId == -1) {
733 throw new IllegalArgumentException("Surface is not part of this session");
736 mRemoteDevice.tearDown(streamId);
737 } catch (CameraRuntimeException e) {
739 } catch (RemoteException e) {
746 public int capture(CaptureRequest request, CaptureCallback callback, Handler handler)
747 throws CameraAccessException {
749 Log.d(TAG, "calling capture");
751 List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
752 requestList.add(request);
753 return submitCaptureRequest(requestList, callback, handler, /*streaming*/false);
756 public int captureBurst(List<CaptureRequest> requests, CaptureCallback callback,
757 Handler handler) throws CameraAccessException {
758 if (requests == null || requests.isEmpty()) {
759 throw new IllegalArgumentException("At least one request must be given");
761 return submitCaptureRequest(requests, callback, handler, /*streaming*/false);
765 * This method checks lastFrameNumber returned from ICameraDeviceUser methods for
766 * starting and stopping repeating request and flushing.
768 * <p>If lastFrameNumber is NO_FRAMES_CAPTURED, it means that the request was never
769 * sent to HAL. Then onCaptureSequenceAborted is immediately triggered.
770 * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber as the last
771 * regular frame number will be added to the list mRequestLastFrameNumbersList.</p>
773 * @param requestId the request ID of the current repeating request.
775 * @param lastFrameNumber last frame number returned from binder.
777 private void checkEarlyTriggerSequenceComplete(
778 final int requestId, final long lastFrameNumber) {
779 // lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request
780 // was never sent to HAL. Should trigger onCaptureSequenceAborted immediately.
781 if (lastFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) {
782 final CaptureCallbackHolder holder;
783 int index = mCaptureCallbackMap.indexOfKey(requestId);
784 holder = (index >= 0) ? mCaptureCallbackMap.valueAt(index) : null;
785 if (holder != null) {
786 mCaptureCallbackMap.removeAt(index);
788 Log.v(TAG, String.format(
789 "remove holder for requestId %d, "
790 + "because lastFrame is %d.",
791 requestId, lastFrameNumber));
795 if (holder != null) {
797 Log.v(TAG, "immediately trigger onCaptureSequenceAborted because"
798 + " request did not reach HAL");
801 Runnable resultDispatch = new Runnable() {
804 if (!CameraDeviceImpl.this.isClosed()) {
806 Log.d(TAG, String.format(
807 "early trigger sequence complete for request %d",
810 holder.getCallback().onCaptureSequenceAborted(
811 CameraDeviceImpl.this,
816 holder.getHandler().post(resultDispatch);
818 Log.w(TAG, String.format(
819 "did not register callback to request %d",
823 // This function is only called for regular request so lastFrameNumber is the last
824 // regular frame number.
825 mRequestLastFrameNumbersList.add(new RequestLastFrameNumbersHolder(requestId,
828 // It is possible that the last frame has already arrived, so we need to check
829 // for sequence completion right away
830 checkAndFireSequenceComplete();
834 private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureCallback callback,
835 Handler handler, boolean repeating) throws CameraAccessException {
837 // Need a valid handler, or current thread needs to have a looper, if
839 handler = checkHandler(handler, callback);
841 // Make sure that there all requests have at least 1 surface; all surfaces are non-null
842 for (CaptureRequest request : requestList) {
843 if (request.getTargets().isEmpty()) {
844 throw new IllegalArgumentException(
845 "Each request must have at least one Surface target");
848 for (Surface surface : request.getTargets()) {
849 if (surface == null) {
850 throw new IllegalArgumentException("Null Surface targets are not allowed");
855 synchronized(mInterfaceLock) {
856 checkIfCameraClosedOrInError();
863 LongParcelable lastFrameNumberRef = new LongParcelable();
865 requestId = mRemoteDevice.submitRequestList(requestList, repeating,
866 /*out*/lastFrameNumberRef);
868 Log.v(TAG, "last frame number " + lastFrameNumberRef.getNumber());
870 } catch (CameraRuntimeException e) {
872 } catch (RemoteException e) {
877 if (callback != null) {
878 mCaptureCallbackMap.put(requestId, new CaptureCallbackHolder(callback,
879 requestList, handler, repeating, mNextSessionId - 1));
882 Log.d(TAG, "Listen for request " + requestId + " is null");
886 long lastFrameNumber = lastFrameNumberRef.getNumber();
889 if (mRepeatingRequestId != REQUEST_ID_NONE) {
890 checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
892 mRepeatingRequestId = requestId;
894 mRequestLastFrameNumbersList.add(new RequestLastFrameNumbersHolder(requestList,
895 requestId, lastFrameNumber));
899 mDeviceHandler.post(mCallOnActive);
907 public int setRepeatingRequest(CaptureRequest request, CaptureCallback callback,
908 Handler handler) throws CameraAccessException {
909 List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
910 requestList.add(request);
911 return submitCaptureRequest(requestList, callback, handler, /*streaming*/true);
914 public int setRepeatingBurst(List<CaptureRequest> requests, CaptureCallback callback,
915 Handler handler) throws CameraAccessException {
916 if (requests == null || requests.isEmpty()) {
917 throw new IllegalArgumentException("At least one request must be given");
919 return submitCaptureRequest(requests, callback, handler, /*streaming*/true);
922 public void stopRepeating() throws CameraAccessException {
924 synchronized(mInterfaceLock) {
925 checkIfCameraClosedOrInError();
926 if (mRepeatingRequestId != REQUEST_ID_NONE) {
928 int requestId = mRepeatingRequestId;
929 mRepeatingRequestId = REQUEST_ID_NONE;
931 // Queue for deletion after in-flight requests finish
932 if (mCaptureCallbackMap.get(requestId) != null) {
933 mRepeatingRequestIdDeletedList.add(requestId);
937 LongParcelable lastFrameNumberRef = new LongParcelable();
938 mRemoteDevice.cancelRequest(requestId, /*out*/lastFrameNumberRef);
939 long lastFrameNumber = lastFrameNumberRef.getNumber();
941 checkEarlyTriggerSequenceComplete(requestId, lastFrameNumber);
943 } catch (CameraRuntimeException e) {
945 } catch (RemoteException e) {
953 private void waitUntilIdle() throws CameraAccessException {
955 synchronized(mInterfaceLock) {
956 checkIfCameraClosedOrInError();
958 if (mRepeatingRequestId != REQUEST_ID_NONE) {
959 throw new IllegalStateException("Active repeating request ongoing");
962 mRemoteDevice.waitUntilIdle();
963 } catch (CameraRuntimeException e) {
965 } catch (RemoteException e) {
972 public void flush() throws CameraAccessException {
973 synchronized(mInterfaceLock) {
974 checkIfCameraClosedOrInError();
976 mDeviceHandler.post(mCallOnBusy);
978 // If already idle, just do a busy->idle transition immediately, don't actually
981 mDeviceHandler.post(mCallOnIdle);
985 LongParcelable lastFrameNumberRef = new LongParcelable();
986 mRemoteDevice.flush(/*out*/lastFrameNumberRef);
987 if (mRepeatingRequestId != REQUEST_ID_NONE) {
988 long lastFrameNumber = lastFrameNumberRef.getNumber();
989 checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
990 mRepeatingRequestId = REQUEST_ID_NONE;
992 } catch (CameraRuntimeException e) {
994 } catch (RemoteException e) {
1002 public void close() {
1003 synchronized (mInterfaceLock) {
1004 if (mClosing.getAndSet(true)) {
1009 if (mRemoteDevice != null) {
1010 mRemoteDevice.disconnect();
1012 } catch (CameraRuntimeException e) {
1013 Log.e(TAG, "Exception while closing: ", e.asChecked());
1014 } catch (RemoteException e) {
1018 // Only want to fire the onClosed callback once;
1019 // either a normal close where the remote device is valid
1020 // or a close after a startup error (no remote device but in error state)
1021 if (mRemoteDevice != null || mInError) {
1022 mDeviceHandler.post(mCallOnClosed);
1025 mRemoteDevice = null;
1030 protected void finalize() throws Throwable {
1039 private void checkInputConfiguration(InputConfiguration inputConfig) {
1040 if (inputConfig != null) {
1041 StreamConfigurationMap configMap = mCharacteristics.get(
1042 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
1044 int[] inputFormats = configMap.getInputFormats();
1045 boolean validFormat = false;
1046 for (int format : inputFormats) {
1047 if (format == inputConfig.getFormat()) {
1052 if (validFormat == false) {
1053 throw new IllegalArgumentException("input format " + inputConfig.getFormat() +
1057 boolean validSize = false;
1058 Size[] inputSizes = configMap.getInputSizes(inputConfig.getFormat());
1059 for (Size s : inputSizes) {
1060 if (inputConfig.getWidth() == s.getWidth() &&
1061 inputConfig.getHeight() == s.getHeight()) {
1066 if (validSize == false) {
1067 throw new IllegalArgumentException("input size " + inputConfig.getWidth() + "x" +
1068 inputConfig.getHeight() + " is not valid");
1074 * <p>A callback for tracking the progress of a {@link CaptureRequest}
1075 * submitted to the camera device.</p>
1078 public static abstract class CaptureCallback {
1081 * This constant is used to indicate that no images were captured for
1086 public static final int NO_FRAMES_CAPTURED = -1;
1089 * This method is called when the camera device has started capturing
1090 * the output image for the request, at the beginning of image exposure.
1092 * @see android.media.MediaActionSound
1094 public void onCaptureStarted(CameraDevice camera,
1095 CaptureRequest request, long timestamp, long frameNumber) {
1096 // default empty implementation
1100 * This method is called when some results from an image capture are
1105 public void onCapturePartial(CameraDevice camera,
1106 CaptureRequest request, CaptureResult result) {
1107 // default empty implementation
1111 * This method is called when an image capture makes partial forward progress; some
1112 * (but not all) results from an image capture are available.
1115 public void onCaptureProgressed(CameraDevice camera,
1116 CaptureRequest request, CaptureResult partialResult) {
1117 // default empty implementation
1121 * This method is called when an image capture has fully completed and all the
1122 * result metadata is available.
1124 public void onCaptureCompleted(CameraDevice camera,
1125 CaptureRequest request, TotalCaptureResult result) {
1126 // default empty implementation
1130 * This method is called instead of {@link #onCaptureCompleted} when the
1131 * camera device failed to produce a {@link CaptureResult} for the
1134 public void onCaptureFailed(CameraDevice camera,
1135 CaptureRequest request, CaptureFailure failure) {
1136 // default empty implementation
1140 * This method is called independently of the others in CaptureCallback,
1141 * when a capture sequence finishes and all {@link CaptureResult}
1142 * or {@link CaptureFailure} for it have been returned via this callback.
1144 public void onCaptureSequenceCompleted(CameraDevice camera,
1145 int sequenceId, long frameNumber) {
1146 // default empty implementation
1150 * This method is called independently of the others in CaptureCallback,
1151 * when a capture sequence aborts before any {@link CaptureResult}
1152 * or {@link CaptureFailure} for it have been returned via this callback.
1154 public void onCaptureSequenceAborted(CameraDevice camera,
1156 // default empty implementation
1161 * A callback for notifications about the state of a camera device, adding in the callbacks that
1162 * were part of the earlier KK API design, but now only used internally.
1164 public static abstract class StateCallbackKK extends StateCallback {
1166 * The method called when a camera device has no outputs configured.
1169 public void onUnconfigured(CameraDevice camera) {
1170 // Default empty implementation
1174 * The method called when a camera device begins processing
1175 * {@link CaptureRequest capture requests}.
1178 public void onActive(CameraDevice camera) {
1179 // Default empty implementation
1183 * The method called when a camera device is busy.
1186 public void onBusy(CameraDevice camera) {
1187 // Default empty implementation
1191 * The method called when a camera device has finished processing all
1192 * submitted capture requests and has reached an idle state.
1195 public void onIdle(CameraDevice camera) {
1196 // Default empty implementation
1200 * The method called when the camera device has finished preparing
1203 public void onSurfacePrepared(Surface surface) {
1204 // Default empty implementation
1208 static class CaptureCallbackHolder {
1210 private final boolean mRepeating;
1211 private final CaptureCallback mCallback;
1212 private final List<CaptureRequest> mRequestList;
1213 private final Handler mHandler;
1214 private final int mSessionId;
1216 CaptureCallbackHolder(CaptureCallback callback, List<CaptureRequest> requestList,
1217 Handler handler, boolean repeating, int sessionId) {
1218 if (callback == null || handler == null) {
1219 throw new UnsupportedOperationException(
1220 "Must have a valid handler and a valid callback");
1222 mRepeating = repeating;
1224 mRequestList = new ArrayList<CaptureRequest>(requestList);
1225 mCallback = callback;
1226 mSessionId = sessionId;
1229 public boolean isRepeating() {
1233 public CaptureCallback getCallback() {
1237 public CaptureRequest getRequest(int subsequenceId) {
1238 if (subsequenceId >= mRequestList.size()) {
1239 throw new IllegalArgumentException(
1241 "Requested subsequenceId %d is larger than request list size %d.",
1242 subsequenceId, mRequestList.size()));
1244 if (subsequenceId < 0) {
1245 throw new IllegalArgumentException(String.format(
1246 "Requested subsequenceId %d is negative", subsequenceId));
1248 return mRequestList.get(subsequenceId);
1253 public CaptureRequest getRequest() {
1254 return getRequest(0);
1257 public Handler getHandler() {
1261 public int getSessionId() {
1267 * This class holds a capture ID and its expected last regular frame number and last reprocess
1270 static class RequestLastFrameNumbersHolder {
1272 private final int mRequestId;
1273 // The last regular frame number for this request ID. It's
1274 // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no regular request.
1275 private final long mLastRegularFrameNumber;
1276 // The last reprocess frame number for this request ID. It's
1277 // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no reprocess request.
1278 private final long mLastReprocessFrameNumber;
1281 * Create a request-last-frame-numbers holder with a list of requests, request ID, and
1282 * the last frame number returned by camera service.
1284 public RequestLastFrameNumbersHolder(List<CaptureRequest> requestList, int requestId,
1285 long lastFrameNumber) {
1286 long lastRegularFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
1287 long lastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
1288 long frameNumber = lastFrameNumber;
1290 if (lastFrameNumber < requestList.size() - 1) {
1291 throw new IllegalArgumentException("lastFrameNumber: " + lastFrameNumber +
1292 " should be at least " + (requestList.size() - 1) + " for the number of " +
1293 " requests in the list: " + requestList.size());
1296 // find the last regular frame number and the last reprocess frame number
1297 for (int i = requestList.size() - 1; i >= 0; i--) {
1298 CaptureRequest request = requestList.get(i);
1299 if (request.isReprocess() && lastReprocessFrameNumber ==
1300 CaptureCallback.NO_FRAMES_CAPTURED) {
1301 lastReprocessFrameNumber = frameNumber;
1302 } else if (!request.isReprocess() && lastRegularFrameNumber ==
1303 CaptureCallback.NO_FRAMES_CAPTURED) {
1304 lastRegularFrameNumber = frameNumber;
1307 if (lastReprocessFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED &&
1308 lastRegularFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED) {
1315 mLastRegularFrameNumber = lastRegularFrameNumber;
1316 mLastReprocessFrameNumber = lastReprocessFrameNumber;
1317 mRequestId = requestId;
1321 * Create a request-last-frame-numbers holder with a request ID and last regular frame
1324 public RequestLastFrameNumbersHolder(int requestId, long lastRegularFrameNumber) {
1325 mLastRegularFrameNumber = lastRegularFrameNumber;
1326 mLastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
1327 mRequestId = requestId;
1331 * Return the last regular frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if
1332 * it contains no regular request.
1334 public long getLastRegularFrameNumber() {
1335 return mLastRegularFrameNumber;
1339 * Return the last reprocess frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if
1340 * it contains no reprocess request.
1342 public long getLastReprocessFrameNumber() {
1343 return mLastReprocessFrameNumber;
1347 * Return the last frame number overall.
1349 public long getLastFrameNumber() {
1350 return Math.max(mLastRegularFrameNumber, mLastReprocessFrameNumber);
1354 * Return the request ID.
1356 public int getRequestId() {
1362 * This class tracks the last frame number for submitted requests.
1364 public class FrameNumberTracker {
1366 private long mCompletedFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
1367 private long mCompletedReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
1368 /** the skipped frame numbers that belong to regular results */
1369 private final LinkedList<Long> mSkippedRegularFrameNumbers = new LinkedList<Long>();
1370 /** the skipped frame numbers that belong to reprocess results */
1371 private final LinkedList<Long> mSkippedReprocessFrameNumbers = new LinkedList<Long>();
1372 /** frame number -> is reprocess */
1373 private final TreeMap<Long, Boolean> mFutureErrorMap = new TreeMap<Long, Boolean>();
1374 /** Map frame numbers to list of partial results */
1375 private final HashMap<Long, List<CaptureResult>> mPartialResults = new HashMap<>();
1377 private void update() {
1378 Iterator iter = mFutureErrorMap.entrySet().iterator();
1379 while (iter.hasNext()) {
1380 TreeMap.Entry pair = (TreeMap.Entry)iter.next();
1381 Long errorFrameNumber = (Long)pair.getKey();
1382 Boolean reprocess = (Boolean)pair.getValue();
1383 Boolean removeError = true;
1385 if (errorFrameNumber == mCompletedReprocessFrameNumber + 1) {
1386 mCompletedReprocessFrameNumber = errorFrameNumber;
1387 } else if (mSkippedReprocessFrameNumbers.isEmpty() != true &&
1388 errorFrameNumber == mSkippedReprocessFrameNumbers.element()) {
1389 mCompletedReprocessFrameNumber = errorFrameNumber;
1390 mSkippedReprocessFrameNumbers.remove();
1392 removeError = false;
1395 if (errorFrameNumber == mCompletedFrameNumber + 1) {
1396 mCompletedFrameNumber = errorFrameNumber;
1397 } else if (mSkippedRegularFrameNumbers.isEmpty() != true &&
1398 errorFrameNumber == mSkippedRegularFrameNumbers.element()) {
1399 mCompletedFrameNumber = errorFrameNumber;
1400 mSkippedRegularFrameNumbers.remove();
1402 removeError = false;
1412 * This function is called every time when a result or an error is received.
1413 * @param frameNumber the frame number corresponding to the result or error
1414 * @param isError true if it is an error, false if it is not an error
1415 * @param isReprocess true if it is a reprocess result, false if it is a regular result.
1417 public void updateTracker(long frameNumber, boolean isError, boolean isReprocess) {
1419 mFutureErrorMap.put(frameNumber, isReprocess);
1423 updateCompletedReprocessFrameNumber(frameNumber);
1425 updateCompletedFrameNumber(frameNumber);
1427 } catch (IllegalArgumentException e) {
1428 Log.e(TAG, e.getMessage());
1435 * This function is called every time a result has been completed.
1437 * <p>It keeps a track of all the partial results already created for a particular
1440 * @param frameNumber the frame number corresponding to the result
1441 * @param result the total or partial result
1442 * @param partial {@true} if the result is partial, {@code false} if total
1443 * @param isReprocess true if it is a reprocess result, false if it is a regular result.
1445 public void updateTracker(long frameNumber, CaptureResult result, boolean partial,
1446 boolean isReprocess) {
1448 // Update the total result's frame status as being successful
1449 updateTracker(frameNumber, /*isError*/false, isReprocess);
1450 // Don't keep a list of total results, we don't need to track them
1454 if (result == null) {
1455 // Do not record blank results; this also means there will be no total result
1456 // so it doesn't matter that the partials were not recorded
1460 // Partial results must be aggregated in-order for that frame number
1461 List<CaptureResult> partials = mPartialResults.get(frameNumber);
1462 if (partials == null) {
1463 partials = new ArrayList<>();
1464 mPartialResults.put(frameNumber, partials);
1467 partials.add(result);
1471 * Attempt to pop off all of the partial results seen so far for the {@code frameNumber}.
1473 * <p>Once popped-off, the partial results are forgotten (unless {@code updateTracker}
1474 * is called again with new partials for that frame number).</p>
1476 * @param frameNumber the frame number corresponding to the result
1477 * @return a list of partial results for that frame with at least 1 element,
1478 * or {@code null} if there were no partials recorded for that frame
1480 public List<CaptureResult> popPartialResults(long frameNumber) {
1481 return mPartialResults.remove(frameNumber);
1484 public long getCompletedFrameNumber() {
1485 return mCompletedFrameNumber;
1488 public long getCompletedReprocessFrameNumber() {
1489 return mCompletedReprocessFrameNumber;
1493 * Update the completed frame number for regular results.
1495 * It validates that all previous frames have arrived except for reprocess frames.
1497 * If there is a gap since previous regular frame number, assume the frames in the gap are
1498 * reprocess frames and store them in the skipped reprocess frame number queue to check
1499 * against when reprocess frames arrive.
1501 private void updateCompletedFrameNumber(long frameNumber) throws IllegalArgumentException {
1502 if (frameNumber <= mCompletedFrameNumber) {
1503 throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat");
1504 } else if (frameNumber <= mCompletedReprocessFrameNumber) {
1505 // if frame number is smaller than completed reprocess frame number,
1506 // it must be the head of mSkippedRegularFrameNumbers
1507 if (mSkippedRegularFrameNumbers.isEmpty() == true ||
1508 frameNumber < mSkippedRegularFrameNumbers.element()) {
1509 throw new IllegalArgumentException("frame number " + frameNumber +
1511 } else if (frameNumber > mSkippedRegularFrameNumbers.element()) {
1512 throw new IllegalArgumentException("frame number " + frameNumber +
1513 " comes out of order. Expecting " +
1514 mSkippedRegularFrameNumbers.element());
1516 // frame number matches the head of the skipped frame number queue.
1517 mSkippedRegularFrameNumbers.remove();
1519 // there is a gap of unseen frame numbers which should belong to reprocess result
1520 // put all the skipped frame numbers in the queue
1521 for (long i = Math.max(mCompletedFrameNumber, mCompletedReprocessFrameNumber) + 1;
1522 i < frameNumber; i++) {
1523 mSkippedReprocessFrameNumbers.add(i);
1527 mCompletedFrameNumber = frameNumber;
1531 * Update the completed frame number for reprocess results.
1533 * It validates that all previous frames have arrived except for regular frames.
1535 * If there is a gap since previous reprocess frame number, assume the frames in the gap are
1536 * regular frames and store them in the skipped regular frame number queue to check
1537 * against when regular frames arrive.
1539 private void updateCompletedReprocessFrameNumber(long frameNumber)
1540 throws IllegalArgumentException {
1541 if (frameNumber < mCompletedReprocessFrameNumber) {
1542 throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat");
1543 } else if (frameNumber < mCompletedFrameNumber) {
1544 // if reprocess frame number is smaller than completed regular frame number,
1545 // it must be the head of the skipped reprocess frame number queue.
1546 if (mSkippedReprocessFrameNumbers.isEmpty() == true ||
1547 frameNumber < mSkippedReprocessFrameNumbers.element()) {
1548 throw new IllegalArgumentException("frame number " + frameNumber +
1550 } else if (frameNumber > mSkippedReprocessFrameNumbers.element()) {
1551 throw new IllegalArgumentException("frame number " + frameNumber +
1552 " comes out of order. Expecting " +
1553 mSkippedReprocessFrameNumbers.element());
1555 // frame number matches the head of the skipped frame number queue.
1556 mSkippedReprocessFrameNumbers.remove();
1558 // put all the skipped frame numbers in the queue
1559 for (long i = Math.max(mCompletedFrameNumber, mCompletedReprocessFrameNumber) + 1;
1560 i < frameNumber; i++) {
1561 mSkippedRegularFrameNumbers.add(i);
1564 mCompletedReprocessFrameNumber = frameNumber;
1568 private void checkAndFireSequenceComplete() {
1569 long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber();
1570 long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber();
1571 boolean isReprocess = false;
1572 Iterator<RequestLastFrameNumbersHolder> iter = mRequestLastFrameNumbersList.iterator();
1573 while (iter.hasNext()) {
1574 final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next();
1575 boolean sequenceCompleted = false;
1576 final int requestId = requestLastFrameNumbers.getRequestId();
1577 final CaptureCallbackHolder holder;
1578 synchronized(mInterfaceLock) {
1579 if (mRemoteDevice == null) {
1580 Log.w(TAG, "Camera closed while checking sequences");
1584 int index = mCaptureCallbackMap.indexOfKey(requestId);
1585 holder = (index >= 0) ?
1586 mCaptureCallbackMap.valueAt(index) : null;
1587 if (holder != null) {
1588 long lastRegularFrameNumber =
1589 requestLastFrameNumbers.getLastRegularFrameNumber();
1590 long lastReprocessFrameNumber =
1591 requestLastFrameNumbers.getLastReprocessFrameNumber();
1593 // check if it's okay to remove request from mCaptureCallbackMap
1594 if (lastRegularFrameNumber <= completedFrameNumber &&
1595 lastReprocessFrameNumber <= completedReprocessFrameNumber) {
1596 sequenceCompleted = true;
1597 mCaptureCallbackMap.removeAt(index);
1599 Log.v(TAG, String.format(
1600 "Remove holder for requestId %d, because lastRegularFrame %d " +
1601 "is <= %d and lastReprocessFrame %d is <= %d", requestId,
1602 lastRegularFrameNumber, completedFrameNumber,
1603 lastReprocessFrameNumber, completedReprocessFrameNumber));
1609 // If no callback is registered for this requestId or sequence completed, remove it
1610 // from the frame number->request pair because it's not needed anymore.
1611 if (holder == null || sequenceCompleted) {
1615 // Call onCaptureSequenceCompleted
1616 if (sequenceCompleted) {
1617 Runnable resultDispatch = new Runnable() {
1620 if (!CameraDeviceImpl.this.isClosed()){
1622 Log.d(TAG, String.format(
1623 "fire sequence complete for request %d",
1627 holder.getCallback().onCaptureSequenceCompleted(
1628 CameraDeviceImpl.this,
1630 requestLastFrameNumbers.getLastFrameNumber());
1634 holder.getHandler().post(resultDispatch);
1639 public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
1641 // Constants below need to be kept up-to-date with
1642 // frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h
1646 // Error codes for onCameraError
1650 * Camera has been disconnected
1652 public static final int ERROR_CAMERA_DISCONNECTED = 0;
1654 * Camera has encountered a device-level error
1655 * Matches CameraDevice.StateCallback#ERROR_CAMERA_DEVICE
1657 public static final int ERROR_CAMERA_DEVICE = 1;
1659 * Camera has encountered a service-level error
1660 * Matches CameraDevice.StateCallback#ERROR_CAMERA_SERVICE
1662 public static final int ERROR_CAMERA_SERVICE = 2;
1664 * Camera has encountered an error processing a single request.
1666 public static final int ERROR_CAMERA_REQUEST = 3;
1668 * Camera has encountered an error producing metadata for a single capture
1670 public static final int ERROR_CAMERA_RESULT = 4;
1672 * Camera has encountered an error producing an image buffer for a single capture
1674 public static final int ERROR_CAMERA_BUFFER = 5;
1677 public IBinder asBinder() {
1682 public void onDeviceError(final int errorCode, CaptureResultExtras resultExtras) {
1684 Log.d(TAG, String.format(
1685 "Device error received, code %d, frame number %d, request ID %d, subseq ID %d",
1686 errorCode, resultExtras.getFrameNumber(), resultExtras.getRequestId(),
1687 resultExtras.getSubsequenceId()));
1690 synchronized(mInterfaceLock) {
1691 if (mRemoteDevice == null) {
1692 return; // Camera already closed
1695 switch (errorCode) {
1696 case ERROR_CAMERA_DISCONNECTED:
1697 CameraDeviceImpl.this.mDeviceHandler.post(mCallOnDisconnected);
1700 Log.e(TAG, "Unknown error from camera device: " + errorCode);
1702 case ERROR_CAMERA_DEVICE:
1703 case ERROR_CAMERA_SERVICE:
1705 Runnable r = new Runnable() {
1708 if (!CameraDeviceImpl.this.isClosed()) {
1709 mDeviceCallback.onError(CameraDeviceImpl.this, errorCode);
1713 CameraDeviceImpl.this.mDeviceHandler.post(r);
1715 case ERROR_CAMERA_REQUEST:
1716 case ERROR_CAMERA_RESULT:
1717 case ERROR_CAMERA_BUFFER:
1718 onCaptureErrorLocked(errorCode, resultExtras);
1725 public void onDeviceIdle() {
1727 Log.d(TAG, "Camera now idle");
1729 synchronized(mInterfaceLock) {
1730 if (mRemoteDevice == null) return; // Camera already closed
1732 if (!CameraDeviceImpl.this.mIdle) {
1733 CameraDeviceImpl.this.mDeviceHandler.post(mCallOnIdle);
1735 CameraDeviceImpl.this.mIdle = true;
1740 public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) {
1741 int requestId = resultExtras.getRequestId();
1742 final long frameNumber = resultExtras.getFrameNumber();
1745 Log.d(TAG, "Capture started for id " + requestId + " frame number " + frameNumber);
1747 final CaptureCallbackHolder holder;
1749 synchronized(mInterfaceLock) {
1750 if (mRemoteDevice == null) return; // Camera already closed
1752 // Get the callback for this frame ID, if there is one
1753 holder = CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId);
1755 if (holder == null) {
1759 if (isClosed()) return;
1761 // Dispatch capture start notice
1762 holder.getHandler().post(
1766 if (!CameraDeviceImpl.this.isClosed()) {
1767 holder.getCallback().onCaptureStarted(
1768 CameraDeviceImpl.this,
1769 holder.getRequest(resultExtras.getSubsequenceId()),
1770 timestamp, frameNumber);
1779 public void onResultReceived(CameraMetadataNative result,
1780 CaptureResultExtras resultExtras) throws RemoteException {
1782 int requestId = resultExtras.getRequestId();
1783 long frameNumber = resultExtras.getFrameNumber();
1786 Log.v(TAG, "Received result frame " + frameNumber + " for id "
1790 synchronized(mInterfaceLock) {
1791 if (mRemoteDevice == null) return; // Camera already closed
1793 // TODO: Handle CameraCharacteristics access from CaptureResult correctly.
1794 result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
1795 getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
1797 final CaptureCallbackHolder holder =
1798 CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId);
1799 final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
1801 boolean isPartialResult =
1802 (resultExtras.getPartialResultCount() < mTotalPartialCount);
1803 boolean isReprocess = request.isReprocess();
1805 // Check if we have a callback for this
1806 if (holder == null) {
1809 "holder is null, early return at frame "
1813 mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
1822 "camera is closed, early return at frame "
1826 mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
1832 Runnable resultDispatch = null;
1834 CaptureResult finalResult;
1836 // Either send a partial result or the final capture completed result
1837 if (isPartialResult) {
1838 final CaptureResult resultAsCapture =
1839 new CaptureResult(result, request, resultExtras);
1842 resultDispatch = new Runnable() {
1845 if (!CameraDeviceImpl.this.isClosed()){
1846 holder.getCallback().onCaptureProgressed(
1847 CameraDeviceImpl.this,
1854 finalResult = resultAsCapture;
1856 List<CaptureResult> partialResults =
1857 mFrameNumberTracker.popPartialResults(frameNumber);
1859 final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result,
1860 request, resultExtras, partialResults, holder.getSessionId());
1862 // Final capture result
1863 resultDispatch = new Runnable() {
1866 if (!CameraDeviceImpl.this.isClosed()){
1867 holder.getCallback().onCaptureCompleted(
1868 CameraDeviceImpl.this,
1875 finalResult = resultAsCapture;
1878 holder.getHandler().post(resultDispatch);
1880 // Collect the partials for a total result; or mark the frame as totally completed
1881 mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult,
1884 // Fire onCaptureSequenceCompleted
1885 if (!isPartialResult) {
1886 checkAndFireSequenceComplete();
1892 public void onPrepared(int streamId) {
1893 final OutputConfiguration output;
1894 final StateCallbackKK sessionCallback;
1897 Log.v(TAG, "Stream " + streamId + " is prepared");
1900 synchronized(mInterfaceLock) {
1901 output = mConfiguredOutputs.get(streamId);
1902 sessionCallback = mSessionStateCallback;
1905 if (sessionCallback == null) return;
1907 if (output == null) {
1908 Log.w(TAG, "onPrepared invoked for unknown output Surface");
1911 final Surface surface = output.getSurface();
1913 sessionCallback.onSurfacePrepared(surface);
1917 * Called by onDeviceError for handling single-capture failures.
1919 private void onCaptureErrorLocked(int errorCode, CaptureResultExtras resultExtras) {
1921 final int requestId = resultExtras.getRequestId();
1922 final int subsequenceId = resultExtras.getSubsequenceId();
1923 final long frameNumber = resultExtras.getFrameNumber();
1924 final CaptureCallbackHolder holder =
1925 CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId);
1927 final CaptureRequest request = holder.getRequest(subsequenceId);
1929 // No way to report buffer errors right now
1930 if (errorCode == ERROR_CAMERA_BUFFER) {
1931 Log.e(TAG, String.format("Lost output buffer reported for frame %d", frameNumber));
1935 boolean mayHaveBuffers = (errorCode == ERROR_CAMERA_RESULT);
1937 // This is only approximate - exact handling needs the camera service and HAL to
1938 // disambiguate between request failures to due abort and due to real errors.
1939 // For now, assume that if the session believes we're mid-abort, then the error
1941 int reason = (mCurrentSession != null && mCurrentSession.isAborting()) ?
1942 CaptureFailure.REASON_FLUSHED :
1943 CaptureFailure.REASON_ERROR;
1945 final CaptureFailure failure = new CaptureFailure(
1948 /*dropped*/ mayHaveBuffers,
1952 Runnable failureDispatch = new Runnable() {
1955 if (!CameraDeviceImpl.this.isClosed()){
1956 holder.getCallback().onCaptureFailed(
1957 CameraDeviceImpl.this,
1963 holder.getHandler().post(failureDispatch);
1965 // Fire onCaptureSequenceCompleted if appropriate
1967 Log.v(TAG, String.format("got error frame %d", frameNumber));
1969 mFrameNumberTracker.updateTracker(frameNumber, /*error*/true, request.isReprocess());
1970 checkAndFireSequenceComplete();
1973 } // public class CameraDeviceCallbacks
1976 * Default handler management.
1979 * If handler is null, get the current thread's
1980 * Looper to create a Handler with. If no looper exists, throw {@code IllegalArgumentException}.
1983 static Handler checkHandler(Handler handler) {
1984 if (handler == null) {
1985 Looper looper = Looper.myLooper();
1986 if (looper == null) {
1987 throw new IllegalArgumentException(
1988 "No handler given, and current thread has no looper!");
1990 handler = new Handler(looper);
1996 * Default handler management, conditional on there being a callback.
1998 * <p>If the callback isn't null, check the handler, otherwise pass it through.</p>
2000 static <T> Handler checkHandler(Handler handler, T callback) {
2001 if (callback != null) {
2002 return checkHandler(handler);
2007 private void checkIfCameraClosedOrInError() throws CameraAccessException {
2008 if (mRemoteDevice == null) {
2009 throw new IllegalStateException("CameraDevice was already closed");
2012 throw new CameraAccessException(CameraAccessException.CAMERA_ERROR,
2013 "The camera device has encountered a serious error");
2017 /** Whether the camera device has started to close (may not yet have finished) */
2018 private boolean isClosed() {
2019 return mClosing.get();
2022 private CameraCharacteristics getCharacteristics() {
2023 return mCharacteristics;