FileDocCategorySizeDatePackage
CameraCaptureSessionImpl.javaAPI DocAndroid 5.1 API27978Thu Mar 12 22:22:10 GMT 2015android.hardware.camera2.impl

CameraCaptureSessionImpl

public class CameraCaptureSessionImpl extends android.hardware.camera2.CameraCaptureSession

Fields Summary
private static final String
TAG
private static final boolean
VERBOSE
private final int
mId
Simple integer ID for session for debugging
private final String
mIdString
private final List
mOutputs
User-specified set of surfaces used as the configuration outputs
private final CameraCaptureSession.StateCallback
mStateCallback
User-specified state callback, used for outgoing events; calls to this object will be automatically {@link Handler#post(Runnable) posted} to {@code mStateHandler}.
private final android.os.Handler
mStateHandler
User-specified state handler used for outgoing state callback events
private final android.hardware.camera2.impl.CameraDeviceImpl
mDeviceImpl
Internal camera device; used to translate calls into existing deprecated API
private final android.os.Handler
mDeviceHandler
Internal handler; used for all incoming events to preserve total order
private final android.hardware.camera2.utils.TaskDrainer
mSequenceDrainer
Drain Sequence IDs which have been queued but not yet finished with aborted/completed
private final android.hardware.camera2.utils.TaskSingleDrainer
mIdleDrainer
Drain state transitions from ACTIVE -> IDLE
private final android.hardware.camera2.utils.TaskSingleDrainer
mAbortDrainer
Drain state transitions from BUSY -> IDLE
private final android.hardware.camera2.utils.TaskSingleDrainer
mUnconfigureDrainer
Drain the UNCONFIGURED state transition
private boolean
mClosed
This session is closed; all further calls will throw ISE
private final boolean
mConfigureSuccess
This session failed to be configured successfully
private boolean
mSkipUnconfigure
Do not unconfigure if this is set; another session will overwrite configuration
private volatile boolean
mAborting
Is the session in the process of aborting? Pay attention to BUSY->IDLE transitions.
Constructors Summary
CameraCaptureSessionImpl(int id, List outputs, CameraCaptureSession.StateCallback callback, android.os.Handler stateHandler, android.hardware.camera2.impl.CameraDeviceImpl deviceImpl, android.os.Handler deviceStateHandler, boolean configureSuccess)
Create a new CameraCaptureSession.

The camera device must already be in the {@code IDLE} state when this is invoked. There must be no pending actions (e.g. no pending captures, no repeating requests, no flush).


                                           
       
               
             
                
        if (outputs == null || outputs.isEmpty()) {
            throw new IllegalArgumentException("outputs must be a non-null, non-empty list");
        } else if (callback == null) {
            throw new IllegalArgumentException("callback must not be null");
        }

        mId = id;
        mIdString = String.format("Session %d: ", mId);

        // TODO: extra verification of outputs
        mOutputs = outputs;
        mStateHandler = checkHandler(stateHandler);
        mStateCallback = createUserStateCallbackProxy(mStateHandler, callback);

        mDeviceHandler = checkNotNull(deviceStateHandler, "deviceStateHandler must not be null");
        mDeviceImpl = checkNotNull(deviceImpl, "deviceImpl must not be null");

        /*
         * Use the same handler as the device's StateCallback for all the internal coming events
         *
         * This ensures total ordering between CameraDevice.StateCallback and
         * CameraDeviceImpl.CaptureCallback events.
         */
        mSequenceDrainer = new TaskDrainer<>(mDeviceHandler, new SequenceDrainListener(),
                /*name*/"seq");
        mIdleDrainer = new TaskSingleDrainer(mDeviceHandler, new IdleDrainListener(),
                /*name*/"idle");
        mAbortDrainer = new TaskSingleDrainer(mDeviceHandler, new AbortDrainListener(),
                /*name*/"abort");
        mUnconfigureDrainer = new TaskSingleDrainer(mDeviceHandler, new UnconfigureDrainListener(),
                /*name*/"unconf");

        // CameraDevice should call configureOutputs and have it finish before constructing us

        if (configureSuccess) {
            mStateCallback.onConfigured(this);
            if (VERBOSE) Log.v(TAG, mIdString + "Created session successfully");
            mConfigureSuccess = true;
        } else {
            mStateCallback.onConfigureFailed(this);
            mClosed = true; // do not fire any other callbacks, do not allow any other work
            Log.e(TAG, mIdString + "Failed to create capture session; configuration failed");
            mConfigureSuccess = false;
        }
    
Methods Summary
public synchronized voidabortCaptures()

        checkNotClosed();

        if (VERBOSE) {
            Log.v(TAG, mIdString + "abortCaptures");
        }

        if (mAborting) {
            Log.w(TAG, mIdString + "abortCaptures - Session is already aborting; doing nothing");
            return;
        }

        mAborting = true;
        mAbortDrainer.taskStarted();

        mDeviceImpl.flush();
        // The next BUSY -> IDLE set of transitions will mark the end of the abort.
    
private intaddPendingSequence(int sequenceId)
Notify the session that a pending capture sequence has just been queued.

During a shutdown/close, the session waits until all pending sessions are finished before taking any further steps to shut down itself.

see
#finishPendingSequence

        mSequenceDrainer.taskStarted(sequenceId);
        return sequenceId;
    
public synchronized intcapture(android.hardware.camera2.CaptureRequest request, CaptureCallback callback, android.os.Handler handler)

        if (request == null) {
            throw new IllegalArgumentException("request must not be null");
        }

        checkNotClosed();

        handler = checkHandler(handler, callback);

        if (VERBOSE) {
            Log.v(TAG, mIdString + "capture - request " + request + ", callback " + callback +
                    " handler " + handler);
        }

        return addPendingSequence(mDeviceImpl.capture(request,
                createCaptureCallbackProxy(handler, callback), mDeviceHandler));
    
public synchronized intcaptureBurst(java.util.List requests, CaptureCallback callback, android.os.Handler handler)

        if (requests == null) {
            throw new IllegalArgumentException("requests must not be null");
        } else if (requests.isEmpty()) {
            throw new IllegalArgumentException("requests must have at least one element");
        }

        checkNotClosed();

        handler = checkHandler(handler, callback);

        if (VERBOSE) {
            CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]);
            Log.v(TAG, mIdString + "captureBurst - requests " + Arrays.toString(requestArray) +
                    ", callback " + callback + " handler " + handler);
        }

        return addPendingSequence(mDeviceImpl.captureBurst(requests,
                createCaptureCallbackProxy(handler, callback), mDeviceHandler));
    
private voidcheckNotClosed()

        if (mClosed) {
            throw new IllegalStateException(
                    "Session has been closed; further changes are illegal.");
        }
    
public synchronized voidclose()


        if (mClosed) {
            if (VERBOSE) Log.v(TAG, mIdString + "close - reentering");
            return;
        }

        if (VERBOSE) Log.v(TAG, mIdString + "close - first time");

        mClosed = true;

        /*
         * Flush out any repeating request. Since camera is closed, no new requests
         * can be queued, and eventually the entire request queue will be drained.
         *
         * If the camera device was already closed, short circuit and do nothing; since
         * no more internal device callbacks will fire anyway.
         *
         * Otherwise, once stopRepeating is done, wait for camera to idle, then unconfigure the
         * camera. Once that's done, fire #onClosed.
         */
        try {
            mDeviceImpl.stopRepeating();
        } catch (IllegalStateException e) {
            // OK: Camera device may already be closed, nothing else to do
            Log.w(TAG, mIdString + "The camera device was already closed: ", e);

            // TODO: Fire onClosed anytime we get the device onClosed or the ISE?
            // or just suppress the ISE only and rely onClosed.
            // Also skip any of the draining work if this is already closed.

            // Short-circuit; queue callback immediately and return
            mStateCallback.onClosed(this);
            return;
        } catch (CameraAccessException e) {
            // OK: close does not throw checked exceptions.
            Log.e(TAG, mIdString + "Exception while stopping repeating: ", e);

            // TODO: call onError instead of onClosed if this happens
        }

        // If no sequences are pending, fire #onClosed immediately
        mSequenceDrainer.beginDrain();
    
private CameraDeviceImpl.CaptureCallbackcreateCaptureCallbackProxy(android.os.Handler handler, CaptureCallback callback)
Forward callbacks from CameraDeviceImpl.CaptureCallback to the CameraCaptureSession.CaptureCallback.

In particular, all calls are automatically split to go both to our own internal callback, and to the user-specified callback (by transparently posting to the user-specified handler).

When a capture sequence finishes, update the pending checked sequences set.

        CameraDeviceImpl.CaptureCallback localCallback = new CameraDeviceImpl.CaptureCallback() {
            @Override
            public void onCaptureSequenceCompleted(CameraDevice camera,
                    int sequenceId, long frameNumber) {
                finishPendingSequence(sequenceId);
            }

            @Override
            public void onCaptureSequenceAborted(CameraDevice camera,
                    int sequenceId) {
                finishPendingSequence(sequenceId);
            }
        };

        /*
         * Split the calls from the device callback into local callback and the following chain:
         * - replace the first CameraDevice arg with a CameraCaptureSession
         * - duck type from device callback to session callback
         * - then forward the call to a handler
         * - then finally invoke the destination method on the session callback object
         */
        if (callback == null) {
            // OK: API allows the user to not specify a callback, and the handler may
            // also be null in that case. Collapse whole dispatch chain to only call the local
            // callback
            return localCallback;
        }

        InvokeDispatcher<CameraDeviceImpl.CaptureCallback> localSink =
                new InvokeDispatcher<>(localCallback);

        InvokeDispatcher<CaptureCallback> userCallbackSink =
                new InvokeDispatcher<>(callback);
        HandlerDispatcher<CaptureCallback> handlerPassthrough =
                new HandlerDispatcher<>(userCallbackSink, handler);
        DuckTypingDispatcher<CameraDeviceImpl.CaptureCallback, CaptureCallback> duckToSession
                = new DuckTypingDispatcher<>(handlerPassthrough, CaptureCallback.class);
        ArgumentReplacingDispatcher<CameraDeviceImpl.CaptureCallback, CameraCaptureSessionImpl>
                replaceDeviceWithSession = new ArgumentReplacingDispatcher<>(duckToSession,
                        /*argumentIndex*/0, this);

        BroadcastDispatcher<CameraDeviceImpl.CaptureCallback> broadcaster =
                new BroadcastDispatcher<CameraDeviceImpl.CaptureCallback>(
                    replaceDeviceWithSession,
                    localSink);

        return new CallbackProxies.DeviceCaptureCallbackProxy(broadcaster);
    
private StateCallbackcreateUserStateCallbackProxy(android.os.Handler handler, StateCallback callback)
Post calls into a CameraCaptureSession.StateCallback to the user-specified {@code handler}.

        InvokeDispatcher<StateCallback> userCallbackSink = new InvokeDispatcher<>(callback);
        HandlerDispatcher<StateCallback> handlerPassthrough =
                new HandlerDispatcher<>(userCallbackSink, handler);

        return new CallbackProxies.SessionStateCallbackProxy(handlerPassthrough);
    
protected voidfinalize()

        try {
            close();
        } finally {
            super.finalize();
        }
    
private voidfinishPendingSequence(int sequenceId)
Notify the session that a pending capture sequence is now finished.

During a shutdown/close, once all pending sequences finish, it is safe to close the camera further by unconfiguring and then firing {@code onClosed}.

        mSequenceDrainer.taskFinished(sequenceId);
    
public android.hardware.camera2.CameraDevicegetDevice()

        return mDeviceImpl;
    
CameraDeviceImpl.StateCallbackKKgetDeviceStateCallback()
Create an internal state callback, to be invoked on the mDeviceHandler

It has a few behaviors:

  • Convert device state changes into session state changes.
  • Keep track of async tasks that the session began (idle, abort).

        final CameraCaptureSession session = this;

        return new CameraDeviceImpl.StateCallbackKK() {
            private boolean mBusy = false;
            private boolean mActive = false;

            @Override
            public void onOpened(CameraDevice camera) {
                throw new AssertionError("Camera must already be open before creating a session");
            }

            @Override
            public void onDisconnected(CameraDevice camera) {
                if (VERBOSE) Log.v(TAG, mIdString + "onDisconnected");
                close();
            }

            @Override
            public void onError(CameraDevice camera, int error) {
                // Should not be reached, handled by device code
                Log.wtf(TAG, mIdString + "Got device error " + error);
            }

            @Override
            public void onActive(CameraDevice camera) {
                mIdleDrainer.taskStarted();
                mActive = true;

                if (VERBOSE) Log.v(TAG, mIdString + "onActive");
                mStateCallback.onActive(session);
            }

            @Override
            public void onIdle(CameraDevice camera) {
                boolean isAborting;
                if (VERBOSE) Log.v(TAG, mIdString + "onIdle");

                synchronized (session) {
                    isAborting = mAborting;
                }

                /*
                 * Check which states we transitioned through:
                 *
                 * (ACTIVE -> IDLE)
                 * (BUSY -> IDLE)
                 *
                 * Note that this is also legal:
                 * (ACTIVE -> BUSY -> IDLE)
                 *
                 * and mark those tasks as finished
                 */
                if (mBusy && isAborting) {
                    mAbortDrainer.taskFinished();

                    synchronized (session) {
                        mAborting = false;
                    }
                }

                if (mActive) {
                    mIdleDrainer.taskFinished();
                }

                mBusy = false;
                mActive = false;

                mStateCallback.onReady(session);
            }

            @Override
            public void onBusy(CameraDevice camera) {
                mBusy = true;

                // TODO: Queue captures during abort instead of failing them
                // since the app won't be able to distinguish the two actives
                // Don't signal the application since there's no clean mapping here
                if (VERBOSE) Log.v(TAG, mIdString + "onBusy");
            }

            @Override
            public void onUnconfigured(CameraDevice camera) {
                if (VERBOSE) Log.v(TAG, mIdString + "onUnconfigured");
                synchronized (session) {
                    // Ignore #onUnconfigured before #close is called.
                    //
                    // Normally, this is reached when this session is closed and no immediate other
                    // activity happens for the camera, in which case the camera is configured to
                    // null streams by this session and the UnconfigureDrainer task is started.
                    // However, we can also end up here if
                    //
                    // 1) Session is closed
                    // 2) New session is created before this session finishes closing, setting
                    //    mSkipUnconfigure and therefore this session does not configure null or
                    //    start the UnconfigureDrainer task.
                    // 3) And then the new session fails to be created, so onUnconfigured fires
                    //    _anyway_.
                    // In this second case, need to not finish a task that was never started, so
                    // guard with mSkipUnconfigure
                    if (mClosed && mConfigureSuccess && !mSkipUnconfigure) {
                        mUnconfigureDrainer.taskFinished();
                    }
                }
            }
        };

    
booleanisAborting()
Whether currently in mid-abort.

This is used by the implementation to set the capture failure reason, in lieu of more accurate error codes from the camera service. Unsynchronized to avoid deadlocks between simultaneous session->device, device->session calls.

Package-private.

        return mAborting;
    
synchronized voidreplaceSessionClose()
Replace this session with another session.

This is an optimization to avoid unconfiguring and then immediately having to reconfigure again.

The semantics are identical to {@link #close}, except that unconfiguring will be skipped.

After this call completes, the session will not call any further methods on the camera device.

see
CameraCaptureSession#close

        /*
         * In order for creating new sessions to be fast, the new session should be created
         * before the old session is closed.
         *
         * Otherwise the old session will always unconfigure if there is no new session to
         * replace it.
         *
         * Unconfiguring could add hundreds of milliseconds of delay. We could race and attempt
         * to skip unconfigure if a new session is created before the captures are all drained,
         * but this would introduce nondeterministic behavior.
         */

        if (VERBOSE) Log.v(TAG, mIdString + "replaceSessionClose");

        // Set up fast shutdown. Possible alternative paths:
        // - This session is active, so close() below starts the shutdown drain
        // - This session is mid-shutdown drain, and hasn't yet reached the idle drain listener.
        // - This session is already closed and has executed the idle drain listener, and
        //   configureOutputsChecked(null) has already been called.
        //
        // Do not call configureOutputsChecked(null) going forward, since it would race with the
        // configuration for the new session. If it was already called, then we don't care, since it
        // won't get called again.
        mSkipUnconfigure = true;

        close();
    
public synchronized intsetRepeatingBurst(java.util.List requests, CaptureCallback callback, android.os.Handler handler)

        if (requests == null) {
            throw new IllegalArgumentException("requests must not be null");
        } else if (requests.isEmpty()) {
            throw new IllegalArgumentException("requests must have at least one element");
        }

        checkNotClosed();

        handler = checkHandler(handler, callback);

        if (VERBOSE) {
            CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]);
            Log.v(TAG, mIdString + "setRepeatingBurst - requests " + Arrays.toString(requestArray) +
                    ", callback " + callback + " handler" + "" + handler);
        }

        return addPendingSequence(mDeviceImpl.setRepeatingBurst(requests,
                createCaptureCallbackProxy(handler, callback), mDeviceHandler));
    
public synchronized intsetRepeatingRequest(android.hardware.camera2.CaptureRequest request, CaptureCallback callback, android.os.Handler handler)

        if (request == null) {
            throw new IllegalArgumentException("request must not be null");
        }

        checkNotClosed();

        handler = checkHandler(handler, callback);

        if (VERBOSE) {
            Log.v(TAG, mIdString + "setRepeatingRequest - request " + request + ", callback " +
                    callback + " handler" + " " + handler);
        }

        return addPendingSequence(mDeviceImpl.setRepeatingRequest(request,
                createCaptureCallbackProxy(handler, callback), mDeviceHandler));
    
public synchronized voidstopRepeating()

        checkNotClosed();

        if (VERBOSE) {
            Log.v(TAG, mIdString + "stopRepeating");
        }

        mDeviceImpl.stopRepeating();