CameraDeviceImplpublic class CameraDeviceImpl extends android.hardware.camera2.CameraDevice HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate |
Fields Summary |
---|
private final String | TAG | private final boolean | DEBUG | private static final int | REQUEST_ID_NONE | private android.hardware.camera2.ICameraDeviceUser | mRemoteDevice | final Object | mInterfaceLock | private final CameraDeviceCallbacks | mCallbacks | private final StateCallback | mDeviceCallback | private volatile StateCallbackKK | mSessionStateCallback | private final android.os.Handler | mDeviceHandler | private volatile boolean | mClosing | private boolean | mInError | private boolean | mIdle | private final android.util.SparseArray | mCaptureCallbackMapmap request IDs to callback/request data | private int | mRepeatingRequestId | private final ArrayList | mRepeatingRequestIdDeletedList | private final android.util.SparseArray | mConfiguredOutputs | private final String | mCameraId | private final android.hardware.camera2.CameraCharacteristics | mCharacteristics | private final int | mTotalPartialCount | private final List | mFrameNumberRequestPairsA list tracking request and its expected last frame.
Updated when calling ICameraDeviceUser methods. | private final FrameNumberTracker | mFrameNumberTrackerAn object tracking received frame numbers.
Updated when receiving callbacks from ICameraDeviceCallbacks. | private CameraCaptureSessionImpl | mCurrentSession | private int | mNextSessionId | private final Runnable | mCallOnOpened | private final Runnable | mCallOnUnconfigured | private final Runnable | mCallOnActive | private final Runnable | mCallOnBusy | private final Runnable | mCallOnClosed | private final Runnable | mCallOnIdle | private final Runnable | mCallOnDisconnected |
Constructors Summary |
---|
public CameraDeviceImpl(String cameraId, StateCallback callback, android.os.Handler handler, android.hardware.camera2.CameraCharacteristics characteristics)
if (cameraId == null || callback == null || handler == null || characteristics == null) {
throw new IllegalArgumentException("Null argument given");
}
mCameraId = cameraId;
mDeviceCallback = callback;
mDeviceHandler = handler;
mCharacteristics = characteristics;
final int MAX_TAG_LEN = 23;
String tag = String.format("CameraDevice-JV-%s", mCameraId);
if (tag.length() > MAX_TAG_LEN) {
tag = tag.substring(0, MAX_TAG_LEN);
}
TAG = tag;
DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Integer partialCount =
mCharacteristics.get(CameraCharacteristics.REQUEST_PARTIAL_RESULT_COUNT);
if (partialCount == null) {
// 1 means partial result is not supported.
mTotalPartialCount = 1;
} else {
mTotalPartialCount = partialCount;
}
|
Methods Summary |
---|
public int | capture(android.hardware.camera2.CaptureRequest request, android.hardware.camera2.impl.CameraDeviceImpl$CaptureCallback callback, android.os.Handler handler)
if (DEBUG) {
Log.d(TAG, "calling capture");
}
List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
requestList.add(request);
return submitCaptureRequest(requestList, callback, handler, /*streaming*/false);
| public int | captureBurst(java.util.List requests, android.hardware.camera2.impl.CameraDeviceImpl$CaptureCallback callback, android.os.Handler handler)
if (requests == null || requests.isEmpty()) {
throw new IllegalArgumentException("At least one request must be given");
}
return submitCaptureRequest(requests, callback, handler, /*streaming*/false);
| private void | checkAndFireSequenceComplete()
long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber();
Iterator<SimpleEntry<Long, Integer> > iter = mFrameNumberRequestPairs.iterator();
while (iter.hasNext()) {
final SimpleEntry<Long, Integer> frameNumberRequestPair = iter.next();
if (frameNumberRequestPair.getKey() <= completedFrameNumber) {
// remove request from mCaptureCallbackMap
final int requestId = frameNumberRequestPair.getValue();
final CaptureCallbackHolder holder;
synchronized(mInterfaceLock) {
if (mRemoteDevice == null) {
Log.w(TAG, "Camera closed while checking sequences");
return;
}
int index = mCaptureCallbackMap.indexOfKey(requestId);
holder = (index >= 0) ? mCaptureCallbackMap.valueAt(index)
: null;
if (holder != null) {
mCaptureCallbackMap.removeAt(index);
if (DEBUG) {
Log.v(TAG, String.format(
"remove holder for requestId %d, "
+ "because lastFrame %d is <= %d",
requestId, frameNumberRequestPair.getKey(),
completedFrameNumber));
}
}
}
iter.remove();
// Call onCaptureSequenceCompleted
if (holder != null) {
Runnable resultDispatch = new Runnable() {
@Override
public void run() {
if (!CameraDeviceImpl.this.isClosed()){
if (DEBUG) {
Log.d(TAG, String.format(
"fire sequence complete for request %d",
requestId));
}
long lastFrameNumber = frameNumberRequestPair.getKey();
if (lastFrameNumber < Integer.MIN_VALUE
|| lastFrameNumber > Integer.MAX_VALUE) {
throw new AssertionError(lastFrameNumber
+ " cannot be cast to int");
}
holder.getCallback().onCaptureSequenceCompleted(
CameraDeviceImpl.this,
requestId,
lastFrameNumber);
}
}
};
holder.getHandler().post(resultDispatch);
}
}
}
| private void | checkEarlyTriggerSequenceComplete(int requestId, long lastFrameNumber)This method checks lastFrameNumber returned from ICameraDeviceUser methods for
starting and stopping repeating request and flushing.
If lastFrameNumber is NO_FRAMES_CAPTURED, it means that the request was never
sent to HAL. Then onCaptureSequenceAborted is immediately triggered.
If lastFrameNumber is non-negative, then the requestId and lastFrameNumber pair
is added to the list mFrameNumberRequestPairs.
// lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request
// was never sent to HAL. Should trigger onCaptureSequenceAborted immediately.
if (lastFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) {
final CaptureCallbackHolder holder;
int index = mCaptureCallbackMap.indexOfKey(requestId);
holder = (index >= 0) ? mCaptureCallbackMap.valueAt(index) : null;
if (holder != null) {
mCaptureCallbackMap.removeAt(index);
if (DEBUG) {
Log.v(TAG, String.format(
"remove holder for requestId %d, "
+ "because lastFrame is %d.",
requestId, lastFrameNumber));
}
}
if (holder != null) {
if (DEBUG) {
Log.v(TAG, "immediately trigger onCaptureSequenceAborted because"
+ " request did not reach HAL");
}
Runnable resultDispatch = new Runnable() {
@Override
public void run() {
if (!CameraDeviceImpl.this.isClosed()) {
if (DEBUG) {
Log.d(TAG, String.format(
"early trigger sequence complete for request %d",
requestId));
}
if (lastFrameNumber < Integer.MIN_VALUE
|| lastFrameNumber > Integer.MAX_VALUE) {
throw new AssertionError(lastFrameNumber + " cannot be cast to int");
}
holder.getCallback().onCaptureSequenceAborted(
CameraDeviceImpl.this,
requestId);
}
}
};
holder.getHandler().post(resultDispatch);
} else {
Log.w(TAG, String.format(
"did not register callback to request %d",
requestId));
}
} else {
mFrameNumberRequestPairs.add(
new SimpleEntry<Long, Integer>(lastFrameNumber,
requestId));
// It is possible that the last frame has already arrived, so we need to check
// for sequence completion right away
checkAndFireSequenceComplete();
}
| static android.os.Handler | checkHandler(android.os.Handler handler)Default handler management.
If handler is null, get the current thread's
Looper to create a Handler with. If no looper exists, throw {@code IllegalArgumentException}.
if (handler == null) {
Looper looper = Looper.myLooper();
if (looper == null) {
throw new IllegalArgumentException(
"No handler given, and current thread has no looper!");
}
handler = new Handler(looper);
}
return handler;
| static android.os.Handler | checkHandler(android.os.Handler handler, T callback)Default handler management, conditional on there being a callback.
If the callback isn't null, check the handler, otherwise pass it through.
if (callback != null) {
return checkHandler(handler);
}
return handler;
| private void | checkIfCameraClosedOrInError()
if (mInError) {
throw new CameraAccessException(CameraAccessException.CAMERA_ERROR,
"The camera device has encountered a serious error");
}
if (mRemoteDevice == null) {
throw new IllegalStateException("CameraDevice was already closed");
}
| public void | close()
synchronized (mInterfaceLock) {
try {
if (mRemoteDevice != null) {
mRemoteDevice.disconnect();
}
} catch (CameraRuntimeException e) {
Log.e(TAG, "Exception while closing: ", e.asChecked());
} catch (RemoteException e) {
// impossible
}
// Only want to fire the onClosed callback once;
// either a normal close where the remote device is valid
// or a close after a startup error (no remote device but in error state)
if (mRemoteDevice != null || mInError) {
mDeviceHandler.post(mCallOnClosed);
}
mRemoteDevice = null;
mInError = false;
}
| public void | configureOutputs(java.util.List outputs)
// Leave this here for backwards compatibility with older code using this directly
configureOutputsChecked(outputs);
| public boolean | configureOutputsChecked(java.util.List outputs)Attempt to configure the outputs; the device goes to idle and then configures the
new outputs if possible.
The configuration may gracefully fail, if there are too many outputs, if the formats
are not supported, or if the sizes for that format is not supported. In this case this
function will return {@code false} and the unconfigured callback will be fired.
If the configuration succeeds (with 1 or more outputs), then the idle callback is fired.
Unconfiguring the device always fires the idle callback.
// Treat a null input the same an empty list
if (outputs == null) {
outputs = new ArrayList<Surface>();
}
boolean success = false;
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
HashSet<Surface> addSet = new HashSet<Surface>(outputs); // Streams to create
List<Integer> deleteList = new ArrayList<Integer>(); // Streams to delete
// Determine which streams need to be created, which to be deleted
for (int i = 0; i < mConfiguredOutputs.size(); ++i) {
int streamId = mConfiguredOutputs.keyAt(i);
Surface s = mConfiguredOutputs.valueAt(i);
if (!outputs.contains(s)) {
deleteList.add(streamId);
} else {
addSet.remove(s); // Don't create a stream previously created
}
}
mDeviceHandler.post(mCallOnBusy);
stopRepeating();
try {
waitUntilIdle();
mRemoteDevice.beginConfigure();
// Delete all streams first (to free up HW resources)
for (Integer streamId : deleteList) {
mRemoteDevice.deleteStream(streamId);
mConfiguredOutputs.delete(streamId);
}
// Add all new streams
for (Surface s : addSet) {
// TODO: remove width,height,format since we are ignoring
// it.
int streamId = mRemoteDevice.createStream(0, 0, 0, s);
mConfiguredOutputs.put(streamId, s);
}
try {
mRemoteDevice.endConfigure();
}
catch (IllegalArgumentException e) {
// OK. camera service can reject stream config if it's not supported by HAL
// This is only the result of a programmer misusing the camera2 api.
Log.w(TAG, "Stream configuration failed");
return false;
}
success = true;
} catch (CameraRuntimeException e) {
if (e.getReason() == CAMERA_IN_USE) {
throw new IllegalStateException("The camera is currently busy." +
" You must wait until the previous operation completes.");
}
throw e.asChecked();
} catch (RemoteException e) {
// impossible
return false;
} finally {
if (success && outputs.size() > 0) {
mDeviceHandler.post(mCallOnIdle);
} else {
// Always return to the 'unconfigured' state if we didn't hit a fatal error
mDeviceHandler.post(mCallOnUnconfigured);
}
}
}
return success;
| public CaptureRequest.Builder | createCaptureRequest(int templateType)
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
CameraMetadataNative templatedRequest = new CameraMetadataNative();
try {
mRemoteDevice.createDefaultRequest(templateType, /*out*/templatedRequest);
} catch (CameraRuntimeException e) {
throw e.asChecked();
} catch (RemoteException e) {
// impossible
return null;
}
CaptureRequest.Builder builder =
new CaptureRequest.Builder(templatedRequest);
return builder;
}
| public void | createCaptureSession(java.util.List outputs, CameraCaptureSession.StateCallback callback, android.os.Handler handler)
synchronized(mInterfaceLock) {
if (DEBUG) {
Log.d(TAG, "createCaptureSession");
}
checkIfCameraClosedOrInError();
// Notify current session that it's going away, before starting camera operations
// After this call completes, the session is not allowed to call into CameraDeviceImpl
if (mCurrentSession != null) {
mCurrentSession.replaceSessionClose();
}
// TODO: dont block for this
boolean configureSuccess = true;
CameraAccessException pendingException = null;
try {
configureSuccess = configureOutputsChecked(outputs); // and then block until IDLE
} catch (CameraAccessException e) {
configureSuccess = false;
pendingException = e;
if (DEBUG) {
Log.v(TAG, "createCaptureSession - failed with exception ", e);
}
}
// Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise.
CameraCaptureSessionImpl newSession =
new CameraCaptureSessionImpl(mNextSessionId++,
outputs, callback, handler, this, mDeviceHandler,
configureSuccess);
// TODO: wait until current session closes, then create the new session
mCurrentSession = newSession;
if (pendingException != null) {
throw pendingException;
}
mSessionStateCallback = mCurrentSession.getDeviceStateCallback();
}
| protected void | finalize()
try {
close();
}
finally {
super.finalize();
}
| public void | flush()
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
mDeviceHandler.post(mCallOnBusy);
// If already idle, just do a busy->idle transition immediately, don't actually
// flush.
if (mIdle) {
mDeviceHandler.post(mCallOnIdle);
return;
}
try {
LongParcelable lastFrameNumberRef = new LongParcelable();
mRemoteDevice.flush(/*out*/lastFrameNumberRef);
if (mRepeatingRequestId != REQUEST_ID_NONE) {
long lastFrameNumber = lastFrameNumberRef.getNumber();
checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
mRepeatingRequestId = REQUEST_ID_NONE;
}
} catch (CameraRuntimeException e) {
throw e.asChecked();
} catch (RemoteException e) {
// impossible
return;
}
}
| public android.hardware.camera2.impl.CameraDeviceImpl$CameraDeviceCallbacks | getCallbacks()
return mCallbacks;
| private android.hardware.camera2.CameraCharacteristics | getCharacteristics()
return mCharacteristics;
| public java.lang.String | getId()
return mCameraId;
| private boolean | isClosed()Whether the camera device has started to close (may not yet have finished)
return mClosing;
| public void | setRemoteDevice(android.hardware.camera2.ICameraDeviceUser remoteDevice)
synchronized(mInterfaceLock) {
// TODO: Move from decorator to direct binder-mediated exceptions
// If setRemoteFailure already called, do nothing
if (mInError) return;
mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice);
mDeviceHandler.post(mCallOnOpened);
mDeviceHandler.post(mCallOnUnconfigured);
}
| public void | setRemoteFailure(android.hardware.camera2.utils.CameraRuntimeException failure)Call to indicate failed connection to a remote camera device.
This places the camera device in the error state and informs the callback.
Use in place of setRemoteDevice() when startup fails.
int failureCode = StateCallback.ERROR_CAMERA_DEVICE;
boolean failureIsError = true;
switch (failure.getReason()) {
case CameraAccessException.CAMERA_IN_USE:
failureCode = StateCallback.ERROR_CAMERA_IN_USE;
break;
case CameraAccessException.MAX_CAMERAS_IN_USE:
failureCode = StateCallback.ERROR_MAX_CAMERAS_IN_USE;
break;
case CameraAccessException.CAMERA_DISABLED:
failureCode = StateCallback.ERROR_CAMERA_DISABLED;
break;
case CameraAccessException.CAMERA_DISCONNECTED:
failureIsError = false;
break;
case CameraAccessException.CAMERA_ERROR:
failureCode = StateCallback.ERROR_CAMERA_DEVICE;
break;
default:
Log.wtf(TAG, "Unknown failure in opening camera device: " + failure.getReason());
break;
}
final int code = failureCode;
final boolean isError = failureIsError;
synchronized(mInterfaceLock) {
mInError = true;
mDeviceHandler.post(new Runnable() {
@Override
public void run() {
if (isError) {
mDeviceCallback.onError(CameraDeviceImpl.this, code);
} else {
mDeviceCallback.onDisconnected(CameraDeviceImpl.this);
}
}
});
}
| public int | setRepeatingBurst(java.util.List requests, android.hardware.camera2.impl.CameraDeviceImpl$CaptureCallback callback, android.os.Handler handler)
if (requests == null || requests.isEmpty()) {
throw new IllegalArgumentException("At least one request must be given");
}
return submitCaptureRequest(requests, callback, handler, /*streaming*/true);
| public int | setRepeatingRequest(android.hardware.camera2.CaptureRequest request, android.hardware.camera2.impl.CameraDeviceImpl$CaptureCallback callback, android.os.Handler handler)
List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
requestList.add(request);
return submitCaptureRequest(requestList, callback, handler, /*streaming*/true);
| public void | setSessionListener(android.hardware.camera2.impl.CameraDeviceImpl$StateCallbackKK sessionCallback)For use by backwards-compatibility code only.
synchronized(mInterfaceLock) {
mSessionStateCallback = sessionCallback;
}
| public void | stopRepeating()
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
if (mRepeatingRequestId != REQUEST_ID_NONE) {
int requestId = mRepeatingRequestId;
mRepeatingRequestId = REQUEST_ID_NONE;
// Queue for deletion after in-flight requests finish
if (mCaptureCallbackMap.get(requestId) != null) {
mRepeatingRequestIdDeletedList.add(requestId);
}
try {
LongParcelable lastFrameNumberRef = new LongParcelable();
mRemoteDevice.cancelRequest(requestId, /*out*/lastFrameNumberRef);
long lastFrameNumber = lastFrameNumberRef.getNumber();
checkEarlyTriggerSequenceComplete(requestId, lastFrameNumber);
} catch (CameraRuntimeException e) {
throw e.asChecked();
} catch (RemoteException e) {
// impossible
return;
}
}
}
| private int | submitCaptureRequest(java.util.List requestList, android.hardware.camera2.impl.CameraDeviceImpl$CaptureCallback callback, android.os.Handler handler, boolean repeating)
// Need a valid handler, or current thread needs to have a looper, if
// callback is valid
handler = checkHandler(handler, callback);
// Make sure that there all requests have at least 1 surface; all surfaces are non-null
for (CaptureRequest request : requestList) {
if (request.getTargets().isEmpty()) {
throw new IllegalArgumentException(
"Each request must have at least one Surface target");
}
for (Surface surface : request.getTargets()) {
if (surface == null) {
throw new IllegalArgumentException("Null Surface targets are not allowed");
}
}
}
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
int requestId;
if (repeating) {
stopRepeating();
}
LongParcelable lastFrameNumberRef = new LongParcelable();
try {
requestId = mRemoteDevice.submitRequestList(requestList, repeating,
/*out*/lastFrameNumberRef);
if (DEBUG) {
Log.v(TAG, "last frame number " + lastFrameNumberRef.getNumber());
}
} catch (CameraRuntimeException e) {
throw e.asChecked();
} catch (RemoteException e) {
// impossible
return -1;
}
if (callback != null) {
mCaptureCallbackMap.put(requestId, new CaptureCallbackHolder(callback,
requestList, handler, repeating));
} else {
if (DEBUG) {
Log.d(TAG, "Listen for request " + requestId + " is null");
}
}
long lastFrameNumber = lastFrameNumberRef.getNumber();
if (repeating) {
if (mRepeatingRequestId != REQUEST_ID_NONE) {
checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
}
mRepeatingRequestId = requestId;
} else {
mFrameNumberRequestPairs.add(
new SimpleEntry<Long, Integer>(lastFrameNumber, requestId));
}
if (mIdle) {
mDeviceHandler.post(mCallOnActive);
}
mIdle = false;
return requestId;
}
| private void | waitUntilIdle()
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
if (mRepeatingRequestId != REQUEST_ID_NONE) {
throw new IllegalStateException("Active repeating request ongoing");
}
try {
mRemoteDevice.waitUntilIdle();
} catch (CameraRuntimeException e) {
throw e.asChecked();
} catch (RemoteException e) {
// impossible
return;
}
}
|
|