CameraCaptureSessionImplpublic class CameraCaptureSessionImpl extends android.hardware.camera2.CameraCaptureSession
Fields Summary |
---|
private static final String | TAG | private static final boolean | VERBOSE | private final int | mIdSimple integer ID for session for debugging | private final String | mIdString | private final List | mOutputsUser-specified set of surfaces used as the configuration outputs | private final CameraCaptureSession.StateCallback | mStateCallbackUser-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 | mStateHandlerUser-specified state handler used for outgoing state callback events | private final android.hardware.camera2.impl.CameraDeviceImpl | mDeviceImplInternal camera device; used to translate calls into existing deprecated API | private final android.os.Handler | mDeviceHandlerInternal handler; used for all incoming events to preserve total order | private final android.hardware.camera2.utils.TaskDrainer | mSequenceDrainerDrain Sequence IDs which have been queued but not yet finished with aborted/completed | private final android.hardware.camera2.utils.TaskSingleDrainer | mIdleDrainerDrain state transitions from ACTIVE -> IDLE | private final android.hardware.camera2.utils.TaskSingleDrainer | mAbortDrainerDrain state transitions from BUSY -> IDLE | private final android.hardware.camera2.utils.TaskSingleDrainer | mUnconfigureDrainerDrain the UNCONFIGURED state transition | private boolean | mClosedThis session is closed; all further calls will throw ISE | private final boolean | mConfigureSuccessThis session failed to be configured successfully | private boolean | mSkipUnconfigureDo not unconfigure if this is set; another session will overwrite configuration | private volatile boolean | mAbortingIs 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 void | abortCaptures()
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 int | addPendingSequence(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.
mSequenceDrainer.taskStarted(sequenceId);
return sequenceId;
| public synchronized int | capture(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 int | captureBurst(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 void | checkNotClosed()
if (mClosed) {
throw new IllegalStateException(
"Session has been closed; further changes are illegal.");
}
| public synchronized void | close()
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.CaptureCallback | createCaptureCallbackProxy(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 StateCallback | createUserStateCallbackProxy(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 void | finalize()
try {
close();
} finally {
super.finalize();
}
| private void | finishPendingSequence(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.CameraDevice | getDevice()
return mDeviceImpl;
| CameraDeviceImpl.StateCallbackKK | getDeviceStateCallback()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();
}
}
}
};
| boolean | isAborting()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 void | replaceSessionClose()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.
/*
* 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 int | setRepeatingBurst(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 int | setRepeatingRequest(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 void | stopRepeating()
checkNotClosed();
if (VERBOSE) {
Log.v(TAG, mIdString + "stopRepeating");
}
mDeviceImpl.stopRepeating();
|
|