blob: cc8c182b867e20dfe183835cdcd5de6c4c5ea71d [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.hardware.camera2.impl;
import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
import android.annotation.NonNull;
import android.hardware.ICameraService;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.utils.SubmitInfo;
import android.hardware.camera2.utils.SurfaceUtils;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Log;
import android.util.Range;
import android.util.Size;
import android.util.SparseArray;
import android.view.Surface;
import com.android.internal.util.Preconditions;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.Executor;
/**
* HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate
*/
public class CameraDeviceImpl extends CameraDevice
implements IBinder.DeathRecipient {
private final String TAG;
private final boolean DEBUG = false;
private static final int REQUEST_ID_NONE = -1;
// TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
private ICameraDeviceUserWrapper mRemoteDevice;
// Lock to synchronize cross-thread access to device public interface
final Object mInterfaceLock = new Object(); // access from this class and Session only!
private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
private final StateCallback mDeviceCallback;
private volatile StateCallbackKK mSessionStateCallback;
private final Executor mDeviceExecutor;
private final AtomicBoolean mClosing = new AtomicBoolean();
private boolean mInError = false;
private boolean mIdle = true;
/** map request IDs to callback/request data */
private final SparseArray<CaptureCallbackHolder> mCaptureCallbackMap =
new SparseArray<CaptureCallbackHolder>();
private int mRepeatingRequestId = REQUEST_ID_NONE;
// Latest repeating request list's types
private int[] mRepeatingRequestTypes;
// Map stream IDs to input/output configurations
private SimpleEntry<Integer, InputConfiguration> mConfiguredInput =
new SimpleEntry<>(REQUEST_ID_NONE, null);
private final SparseArray<OutputConfiguration> mConfiguredOutputs =
new SparseArray<>();
private final String mCameraId;
private final CameraCharacteristics mCharacteristics;
private final int mTotalPartialCount;
private static final long NANO_PER_SECOND = 1000000000; //ns
/**
* A list tracking request and its expected last regular/reprocess/zslStill frame
* number. Updated when calling ICameraDeviceUser methods.
*/
private final List<RequestLastFrameNumbersHolder> mRequestLastFrameNumbersList =
new ArrayList<>();
/**
* An object tracking received frame numbers.
* Updated when receiving callbacks from ICameraDeviceCallbacks.
*/
private final FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker();
private CameraCaptureSessionCore mCurrentSession;
private int mNextSessionId = 0;
private final int mAppTargetSdkVersion;
// Runnables for all state transitions, except error, which needs the
// error code argument
private final Runnable mCallOnOpened = new Runnable() {
@Override
public void run() {
StateCallbackKK sessionCallback = null;
synchronized(mInterfaceLock) {
if (mRemoteDevice == null) return; // Camera already closed
sessionCallback = mSessionStateCallback;
}
if (sessionCallback != null) {
sessionCallback.onOpened(CameraDeviceImpl.this);
}
mDeviceCallback.onOpened(CameraDeviceImpl.this);
}
};
private final Runnable mCallOnUnconfigured = new Runnable() {
@Override
public void run() {
StateCallbackKK sessionCallback = null;
synchronized(mInterfaceLock) {
if (mRemoteDevice == null) return; // Camera already closed
sessionCallback = mSessionStateCallback;
}
if (sessionCallback != null) {
sessionCallback.onUnconfigured(CameraDeviceImpl.this);
}
}
};
private final Runnable mCallOnActive = new Runnable() {
@Override
public void run() {
StateCallbackKK sessionCallback = null;
synchronized(mInterfaceLock) {
if (mRemoteDevice == null) return; // Camera already closed
sessionCallback = mSessionStateCallback;
}
if (sessionCallback != null) {
sessionCallback.onActive(CameraDeviceImpl.this);
}
}
};
private final Runnable mCallOnBusy = new Runnable() {
@Override
public void run() {
StateCallbackKK sessionCallback = null;
synchronized(mInterfaceLock) {
if (mRemoteDevice == null) return; // Camera already closed
sessionCallback = mSessionStateCallback;
}
if (sessionCallback != null) {
sessionCallback.onBusy(CameraDeviceImpl.this);
}
}
};
private final Runnable mCallOnClosed = new Runnable() {
private boolean mClosedOnce = false;
@Override
public void run() {
if (mClosedOnce) {
throw new AssertionError("Don't post #onClosed more than once");
}
StateCallbackKK sessionCallback = null;
synchronized(mInterfaceLock) {
sessionCallback = mSessionStateCallback;
}
if (sessionCallback != null) {
sessionCallback.onClosed(CameraDeviceImpl.this);
}
mDeviceCallback.onClosed(CameraDeviceImpl.this);
mClosedOnce = true;
}
};
private final Runnable mCallOnIdle = new Runnable() {
@Override
public void run() {
StateCallbackKK sessionCallback = null;
synchronized(mInterfaceLock) {
if (mRemoteDevice == null) return; // Camera already closed
sessionCallback = mSessionStateCallback;
}
if (sessionCallback != null) {
sessionCallback.onIdle(CameraDeviceImpl.this);
}
}
};
private final Runnable mCallOnDisconnected = new Runnable() {
@Override
public void run() {
StateCallbackKK sessionCallback = null;
synchronized(mInterfaceLock) {
if (mRemoteDevice == null) return; // Camera already closed
sessionCallback = mSessionStateCallback;
}
if (sessionCallback != null) {
sessionCallback.onDisconnected(CameraDeviceImpl.this);
}
mDeviceCallback.onDisconnected(CameraDeviceImpl.this);
}
};
public CameraDeviceImpl(String cameraId, StateCallback callback, Executor executor,
CameraCharacteristics characteristics, int appTargetSdkVersion) {
if (cameraId == null || callback == null || executor == null || characteristics == null) {
throw new IllegalArgumentException("Null argument given");
}
mCameraId = cameraId;
mDeviceCallback = callback;
mDeviceExecutor = executor;
mCharacteristics = characteristics;
mAppTargetSdkVersion = appTargetSdkVersion;
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;
Integer partialCount =
mCharacteristics.get(CameraCharacteristics.REQUEST_PARTIAL_RESULT_COUNT);
if (partialCount == null) {
// 1 means partial result is not supported.
mTotalPartialCount = 1;
} else {
mTotalPartialCount = partialCount;
}
}
public CameraDeviceCallbacks getCallbacks() {
return mCallbacks;
}
/**
* Set remote device, which triggers initial onOpened/onUnconfigured callbacks
*
* <p>This function may post onDisconnected and throw CAMERA_DISCONNECTED if remoteDevice dies
* during setup.</p>
*
*/
public void setRemoteDevice(ICameraDeviceUser remoteDevice) throws CameraAccessException {
synchronized(mInterfaceLock) {
// TODO: Move from decorator to direct binder-mediated exceptions
// If setRemoteFailure already called, do nothing
if (mInError) return;
mRemoteDevice = new ICameraDeviceUserWrapper(remoteDevice);
IBinder remoteDeviceBinder = remoteDevice.asBinder();
// For legacy camera device, remoteDevice is in the same process, and
// asBinder returns NULL.
if (remoteDeviceBinder != null) {
try {
remoteDeviceBinder.linkToDeath(this, /*flag*/ 0);
} catch (RemoteException e) {
CameraDeviceImpl.this.mDeviceExecutor.execute(mCallOnDisconnected);
throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
"The camera device has encountered a serious error");
}
}
mDeviceExecutor.execute(mCallOnOpened);
mDeviceExecutor.execute(mCallOnUnconfigured);
}
}
/**
* Call to indicate failed connection to a remote camera device.
*
* <p>This places the camera device in the error state and informs the callback.
* Use in place of setRemoteDevice() when startup fails.</p>
*/
public void setRemoteFailure(final ServiceSpecificException failure) {
int failureCode = StateCallback.ERROR_CAMERA_DEVICE;
boolean failureIsError = true;
switch (failure.errorCode) {
case ICameraService.ERROR_CAMERA_IN_USE:
failureCode = StateCallback.ERROR_CAMERA_IN_USE;
break;
case ICameraService.ERROR_MAX_CAMERAS_IN_USE:
failureCode = StateCallback.ERROR_MAX_CAMERAS_IN_USE;
break;
case ICameraService.ERROR_DISABLED:
failureCode = StateCallback.ERROR_CAMERA_DISABLED;
break;
case ICameraService.ERROR_DISCONNECTED:
failureIsError = false;
break;
case ICameraService.ERROR_INVALID_OPERATION:
failureCode = StateCallback.ERROR_CAMERA_DEVICE;
break;
default:
Log.e(TAG, "Unexpected failure in opening camera device: " + failure.errorCode +
failure.getMessage());
break;
}
final int code = failureCode;
final boolean isError = failureIsError;
synchronized(mInterfaceLock) {
mInError = true;
mDeviceExecutor.execute(new Runnable() {
@Override
public void run() {
if (isError) {
mDeviceCallback.onError(CameraDeviceImpl.this, code);
} else {
mDeviceCallback.onDisconnected(CameraDeviceImpl.this);
}
}
});
}
}
@Override
public String getId() {
return mCameraId;
}
public void configureOutputs(List<Surface> outputs) throws CameraAccessException {
// Leave this here for backwards compatibility with older code using this directly
ArrayList<OutputConfiguration> outputConfigs = new ArrayList<>(outputs.size());
for (Surface s : outputs) {
outputConfigs.add(new OutputConfiguration(s));
}
configureStreamsChecked(/*inputConfig*/null, outputConfigs,
/*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null);
}
/**
* Attempt to configure the input and outputs; the device goes to idle and then configures the
* new input and outputs if possible.
*
* <p>The configuration may gracefully fail, if input configuration is not supported,
* 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.</p>
*
* <p>If the configuration succeeds (with 1 or more outputs with or without an input),
* then the idle callback is fired. Unconfiguring the device always fires the idle callback.</p>
*
* @param inputConfig input configuration or {@code null} for no input
* @param outputs a list of one or more surfaces, or {@code null} to unconfigure
* @param operatingMode If the stream configuration is for a normal session,
* a constrained high speed session, or something else.
* @param sessionParams Session parameters.
* @return whether or not the configuration was successful
*
* @throws CameraAccessException if there were any unexpected problems during configuration
*/
public boolean configureStreamsChecked(InputConfiguration inputConfig,
List<OutputConfiguration> outputs, int operatingMode, CaptureRequest sessionParams)
throws CameraAccessException {
// Treat a null input the same an empty list
if (outputs == null) {
outputs = new ArrayList<OutputConfiguration>();
}
if (outputs.size() == 0 && inputConfig != null) {
throw new IllegalArgumentException("cannot configure an input stream without " +
"any output streams");
}
checkInputConfiguration(inputConfig);
boolean success = false;
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
// Streams to create
HashSet<OutputConfiguration> addSet = new HashSet<OutputConfiguration>(outputs);
// Streams to delete
List<Integer> deleteList = new ArrayList<Integer>();
// Determine which streams need to be created, which to be deleted
for (int i = 0; i < mConfiguredOutputs.size(); ++i) {
int streamId = mConfiguredOutputs.keyAt(i);
OutputConfiguration outConfig = mConfiguredOutputs.valueAt(i);
if (!outputs.contains(outConfig) || outConfig.isDeferredConfiguration()) {
// Always delete the deferred output configuration when the session
// is created, as the deferred output configuration doesn't have unique surface
// related identifies.
deleteList.add(streamId);
} else {
addSet.remove(outConfig); // Don't create a stream previously created
}
}
mDeviceExecutor.execute(mCallOnBusy);
stopRepeating();
try {
waitUntilIdle();
mRemoteDevice.beginConfigure();
// reconfigure the input stream if the input configuration is different.
InputConfiguration currentInputConfig = mConfiguredInput.getValue();
if (inputConfig != currentInputConfig &&
(inputConfig == null || !inputConfig.equals(currentInputConfig))) {
if (currentInputConfig != null) {
mRemoteDevice.deleteStream(mConfiguredInput.getKey());
mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>(
REQUEST_ID_NONE, null);
}
if (inputConfig != null) {
int streamId = mRemoteDevice.createInputStream(inputConfig.getWidth(),
inputConfig.getHeight(), inputConfig.getFormat());
mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>(
streamId, inputConfig);
}
}
// Delete all streams first (to free up HW resources)
for (Integer streamId : deleteList) {
mRemoteDevice.deleteStream(streamId);
mConfiguredOutputs.delete(streamId);
}
// Add all new streams
for (OutputConfiguration outConfig : outputs) {
if (addSet.contains(outConfig)) {
int streamId = mRemoteDevice.createStream(outConfig);
mConfiguredOutputs.put(streamId, outConfig);
}
}
if (sessionParams != null) {
mRemoteDevice.endConfigure(operatingMode, sessionParams.getNativeCopy());
} else {
mRemoteDevice.endConfigure(operatingMode, null);
}
success = true;
} 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 due to: " + e.getMessage());
return false;
} catch (CameraAccessException e) {
if (e.getReason() == CameraAccessException.CAMERA_IN_USE) {
throw new IllegalStateException("The camera is currently busy." +
" You must wait until the previous operation completes.", e);
}
throw e;
} finally {
if (success && outputs.size() > 0) {
mDeviceExecutor.execute(mCallOnIdle);
} else {
// Always return to the 'unconfigured' state if we didn't hit a fatal error
mDeviceExecutor.execute(mCallOnUnconfigured);
}
}
}
return success;
}
@Override
public void createCaptureSession(List<Surface> outputs,
CameraCaptureSession.StateCallback callback, Handler handler)
throws CameraAccessException {
List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size());
for (Surface surface : outputs) {
outConfigurations.add(new OutputConfiguration(surface));
}
createCaptureSessionInternal(null, outConfigurations, callback,
checkAndWrapHandler(handler), /*operatingMode*/ICameraDeviceUser.NORMAL_MODE,
/*sessionParams*/ null);
}
@Override
public void createCaptureSessionByOutputConfigurations(
List<OutputConfiguration> outputConfigurations,
CameraCaptureSession.StateCallback callback, Handler handler)
throws CameraAccessException {
if (DEBUG) {
Log.d(TAG, "createCaptureSessionByOutputConfigurations");
}
// OutputConfiguration objects are immutable, but need to have our own array
List<OutputConfiguration> currentOutputs = new ArrayList<>(outputConfigurations);
createCaptureSessionInternal(null, currentOutputs, callback, checkAndWrapHandler(handler),
/*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/null);
}
@Override
public void createReprocessableCaptureSession(InputConfiguration inputConfig,
List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler)
throws CameraAccessException {
if (DEBUG) {
Log.d(TAG, "createReprocessableCaptureSession");
}
if (inputConfig == null) {
throw new IllegalArgumentException("inputConfig cannot be null when creating a " +
"reprocessable capture session");
}
List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size());
for (Surface surface : outputs) {
outConfigurations.add(new OutputConfiguration(surface));
}
createCaptureSessionInternal(inputConfig, outConfigurations, callback,
checkAndWrapHandler(handler), /*operatingMode*/ICameraDeviceUser.NORMAL_MODE,
/*sessionParams*/ null);
}
@Override
public void createReprocessableCaptureSessionByConfigurations(InputConfiguration inputConfig,
List<OutputConfiguration> outputs,
android.hardware.camera2.CameraCaptureSession.StateCallback callback, Handler handler)
throws CameraAccessException {
if (DEBUG) {
Log.d(TAG, "createReprocessableCaptureSessionWithConfigurations");
}
if (inputConfig == null) {
throw new IllegalArgumentException("inputConfig cannot be null when creating a " +
"reprocessable capture session");
}
if (outputs == null) {
throw new IllegalArgumentException("Output configurations cannot be null when " +
"creating a reprocessable capture session");
}
// OutputConfiguration objects aren't immutable, make a copy before using.
List<OutputConfiguration> currentOutputs = new ArrayList<OutputConfiguration>();
for (OutputConfiguration output : outputs) {
currentOutputs.add(new OutputConfiguration(output));
}
createCaptureSessionInternal(inputConfig, currentOutputs,
callback, checkAndWrapHandler(handler),
/*operatingMode*/ICameraDeviceUser.NORMAL_MODE, /*sessionParams*/ null);
}
@Override
public void createConstrainedHighSpeedCaptureSession(List<Surface> outputs,
android.hardware.camera2.CameraCaptureSession.StateCallback callback, Handler handler)
throws CameraAccessException {
if (outputs == null || outputs.size() == 0 || outputs.size() > 2) {
throw new IllegalArgumentException(
"Output surface list must not be null and the size must be no more than 2");
}
List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size());
for (Surface surface : outputs) {
outConfigurations.add(new OutputConfiguration(surface));
}
createCaptureSessionInternal(null, outConfigurations, callback,
checkAndWrapHandler(handler),
/*operatingMode*/ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE,
/*sessionParams*/ null);
}
@Override
public void createCustomCaptureSession(InputConfiguration inputConfig,
List<OutputConfiguration> outputs,
int operatingMode,
android.hardware.camera2.CameraCaptureSession.StateCallback callback,
Handler handler) throws CameraAccessException {
List<OutputConfiguration> currentOutputs = new ArrayList<OutputConfiguration>();
for (OutputConfiguration output : outputs) {
currentOutputs.add(new OutputConfiguration(output));
}
createCaptureSessionInternal(inputConfig, currentOutputs, callback,
checkAndWrapHandler(handler), operatingMode, /*sessionParams*/ null);
}
@Override
public void createCaptureSession(SessionConfiguration config)
throws CameraAccessException {
if (config == null) {
throw new IllegalArgumentException("Invalid session configuration");
}
List<OutputConfiguration> outputConfigs = config.getOutputConfigurations();
if (outputConfigs == null) {
throw new IllegalArgumentException("Invalid output configurations");
}
if (config.getExecutor() == null) {
throw new IllegalArgumentException("Invalid executor");
}
createCaptureSessionInternal(config.getInputConfiguration(), outputConfigs,
config.getStateCallback(), config.getExecutor(), config.getSessionType(),
config.getSessionParameters());
}
private void createCaptureSessionInternal(InputConfiguration inputConfig,
List<OutputConfiguration> outputConfigurations,
CameraCaptureSession.StateCallback callback, Executor executor,
int operatingMode, CaptureRequest sessionParams) throws CameraAccessException {
synchronized(mInterfaceLock) {
if (DEBUG) {
Log.d(TAG, "createCaptureSessionInternal");
}
checkIfCameraClosedOrInError();
boolean isConstrainedHighSpeed =
(operatingMode == ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE);
if (isConstrainedHighSpeed && inputConfig != null) {
throw new IllegalArgumentException("Constrained high speed session doesn't support"
+ " input configuration yet.");
}
// 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;
Surface input = null;
try {
// configure streams and then block until IDLE
configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations,
operatingMode, sessionParams);
if (configureSuccess == true && inputConfig != null) {
input = mRemoteDevice.getInputSurface();
}
} catch (CameraAccessException e) {
configureSuccess = false;
pendingException = e;
input = null;
if (DEBUG) {
Log.v(TAG, "createCaptureSession - failed with exception ", e);
}
}
// Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise.
CameraCaptureSessionCore newSession = null;
if (isConstrainedHighSpeed) {
ArrayList<Surface> surfaces = new ArrayList<>(outputConfigurations.size());
for (OutputConfiguration outConfig : outputConfigurations) {
surfaces.add(outConfig.getSurface());
}
StreamConfigurationMap config =
getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
SurfaceUtils.checkConstrainedHighSpeedSurfaces(surfaces, /*fpsRange*/null, config);
newSession = new CameraConstrainedHighSpeedCaptureSessionImpl(mNextSessionId++,
callback, executor, this, mDeviceExecutor, configureSuccess,
mCharacteristics);
} else {
newSession = new CameraCaptureSessionImpl(mNextSessionId++, input,
callback, executor, this, mDeviceExecutor, configureSuccess);
}
// TODO: wait until current session closes, then create the new session
mCurrentSession = newSession;
if (pendingException != null) {
throw pendingException;
}
mSessionStateCallback = mCurrentSession.getDeviceStateCallback();
}
}
@Override
public boolean isSessionConfigurationSupported(
@NonNull SessionConfiguration sessionConfig) throws CameraAccessException,
UnsupportedOperationException, IllegalArgumentException {
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
return mRemoteDevice.isSessionConfigurationSupported(sessionConfig);
}
}
/**
* For use by backwards-compatibility code only.
*/
public void setSessionListener(StateCallbackKK sessionCallback) {
synchronized(mInterfaceLock) {
mSessionStateCallback = sessionCallback;
}
}
private void overrideEnableZsl(CameraMetadataNative request, boolean newValue) {
Boolean enableZsl = request.get(CaptureRequest.CONTROL_ENABLE_ZSL);
if (enableZsl == null) {
// If enableZsl is not available, don't override.
return;
}
request.set(CaptureRequest.CONTROL_ENABLE_ZSL, newValue);
}
@Override
public CaptureRequest.Builder createCaptureRequest(int templateType,
Set<String> physicalCameraIdSet)
throws CameraAccessException {
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
for (String physicalId : physicalCameraIdSet) {
if (physicalId == getId()) {
throw new IllegalStateException("Physical id matches the logical id!");
}
}
CameraMetadataNative templatedRequest = null;
templatedRequest = mRemoteDevice.createDefaultRequest(templateType);
// If app target SDK is older than O, or it's not a still capture template, enableZsl
// must be false in the default request.
if (mAppTargetSdkVersion < Build.VERSION_CODES.O ||
templateType != TEMPLATE_STILL_CAPTURE) {
overrideEnableZsl(templatedRequest, false);
}
CaptureRequest.Builder builder = new CaptureRequest.Builder(
templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE,
getId(), physicalCameraIdSet);
return builder;
}
}
@Override
public CaptureRequest.Builder createCaptureRequest(int templateType)
throws CameraAccessException {
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
CameraMetadataNative templatedRequest = null;
templatedRequest = mRemoteDevice.createDefaultRequest(templateType);
// If app target SDK is older than O, or it's not a still capture template, enableZsl
// must be false in the default request.
if (mAppTargetSdkVersion < Build.VERSION_CODES.O ||
templateType != TEMPLATE_STILL_CAPTURE) {
overrideEnableZsl(templatedRequest, false);
}
CaptureRequest.Builder builder = new CaptureRequest.Builder(
templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE,
getId(), /*physicalCameraIdSet*/ null);
return builder;
}
}
@Override
public CaptureRequest.Builder createReprocessCaptureRequest(TotalCaptureResult inputResult)
throws CameraAccessException {
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
CameraMetadataNative resultMetadata = new
CameraMetadataNative(inputResult.getNativeCopy());
return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true,
inputResult.getSessionId(), getId(), /*physicalCameraIdSet*/ null);
}
}
public void prepare(Surface surface) throws CameraAccessException {
if (surface == null) throw new IllegalArgumentException("Surface is null");
synchronized(mInterfaceLock) {
int streamId = -1;
for (int i = 0; i < mConfiguredOutputs.size(); i++) {
final List<Surface> surfaces = mConfiguredOutputs.valueAt(i).getSurfaces();
if (surfaces.contains(surface)) {
streamId = mConfiguredOutputs.keyAt(i);
break;
}
}
if (streamId == -1) {
throw new IllegalArgumentException("Surface is not part of this session");
}
mRemoteDevice.prepare(streamId);
}
}
public void prepare(int maxCount, Surface surface) throws CameraAccessException {
if (surface == null) throw new IllegalArgumentException("Surface is null");
if (maxCount <= 0) throw new IllegalArgumentException("Invalid maxCount given: " +
maxCount);
synchronized(mInterfaceLock) {
int streamId = -1;
for (int i = 0; i < mConfiguredOutputs.size(); i++) {
if (surface == mConfiguredOutputs.valueAt(i).getSurface()) {
streamId = mConfiguredOutputs.keyAt(i);
break;
}
}
if (streamId == -1) {
throw new IllegalArgumentException("Surface is not part of this session");
}
mRemoteDevice.prepare2(maxCount, streamId);
}
}
public void updateOutputConfiguration(OutputConfiguration config)
throws CameraAccessException {
synchronized(mInterfaceLock) {
int streamId = -1;
for (int i = 0; i < mConfiguredOutputs.size(); i++) {
if (config.getSurface() == mConfiguredOutputs.valueAt(i).getSurface()) {
streamId = mConfiguredOutputs.keyAt(i);
break;
}
}
if (streamId == -1) {
throw new IllegalArgumentException("Invalid output configuration");
}
mRemoteDevice.updateOutputConfiguration(streamId, config);
mConfiguredOutputs.put(streamId, config);
}
}
public void tearDown(Surface surface) throws CameraAccessException {
if (surface == null) throw new IllegalArgumentException("Surface is null");
synchronized(mInterfaceLock) {
int streamId = -1;
for (int i = 0; i < mConfiguredOutputs.size(); i++) {
if (surface == mConfiguredOutputs.valueAt(i).getSurface()) {
streamId = mConfiguredOutputs.keyAt(i);
break;
}
}
if (streamId == -1) {
throw new IllegalArgumentException("Surface is not part of this session");
}
mRemoteDevice.tearDown(streamId);
}
}
public void finalizeOutputConfigs(List<OutputConfiguration> outputConfigs)
throws CameraAccessException {
if (outputConfigs == null || outputConfigs.size() == 0) {
throw new IllegalArgumentException("deferred config is null or empty");
}
synchronized(mInterfaceLock) {
for (OutputConfiguration config : outputConfigs) {
int streamId = -1;
for (int i = 0; i < mConfiguredOutputs.size(); i++) {
// Have to use equal here, as createCaptureSessionByOutputConfigurations() and
// createReprocessableCaptureSessionByConfigurations() do a copy of the configs.
if (config.equals(mConfiguredOutputs.valueAt(i))) {
streamId = mConfiguredOutputs.keyAt(i);
break;
}
}
if (streamId == -1) {
throw new IllegalArgumentException("Deferred config is not part of this "
+ "session");
}
if (config.getSurfaces().size() == 0) {
throw new IllegalArgumentException("The final config for stream " + streamId
+ " must have at least 1 surface");
}
mRemoteDevice.finalizeOutputConfigurations(streamId, config);
mConfiguredOutputs.put(streamId, config);
}
}
}
public int capture(CaptureRequest request, CaptureCallback callback, Executor executor)
throws CameraAccessException {
if (DEBUG) {
Log.d(TAG, "calling capture");
}
List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
requestList.add(request);
return submitCaptureRequest(requestList, callback, executor, /*streaming*/false);
}
public int captureBurst(List<CaptureRequest> requests, CaptureCallback callback,
Executor executor) throws CameraAccessException {
if (requests == null || requests.isEmpty()) {
throw new IllegalArgumentException("At least one request must be given");
}
return submitCaptureRequest(requests, callback, executor, /*streaming*/false);
}
/**
* This method checks lastFrameNumber returned from ICameraDeviceUser methods for
* starting and stopping repeating request and flushing.
*
* <p>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 as the last
* regular frame number will be added to the list mRequestLastFrameNumbersList.</p>
*
* @param requestId the request ID of the current repeating request.
* @param lastFrameNumber last frame number returned from binder.
* @param repeatingRequestTypes the repeating requests' types.
*/
private void checkEarlyTriggerSequenceComplete(
final int requestId, final long lastFrameNumber,
final int[] repeatingRequestTypes) {
// 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));
}
holder.getCallback().onCaptureSequenceAborted(
CameraDeviceImpl.this,
requestId);
}
}
};
final long ident = Binder.clearCallingIdentity();
try {
holder.getExecutor().execute(resultDispatch);
} finally {
Binder.restoreCallingIdentity(ident);
}
} else {
Log.w(TAG, String.format(
"did not register callback to request %d",
requestId));
}
} else {
// This function is only called for regular/ZslStill request so lastFrameNumber is the
// last regular/ZslStill frame number.
mRequestLastFrameNumbersList.add(new RequestLastFrameNumbersHolder(requestId,
lastFrameNumber, repeatingRequestTypes));
// It is possible that the last frame has already arrived, so we need to check
// for sequence completion right away
checkAndFireSequenceComplete();
}
}
private int[] getRequestTypes(final CaptureRequest[] requestArray) {
int[] requestTypes = new int[requestArray.length];
for (int i = 0; i < requestArray.length; i++) {
requestTypes[i] = requestArray[i].getRequestType();
}
return requestTypes;
}
private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureCallback callback,
Executor executor, boolean repeating) throws CameraAccessException {
// Need a valid executor, or current thread needs to have a looper, if
// callback is valid
executor = checkExecutor(executor, callback);
// Make sure that there all requests have at least 1 surface; all surfaces are non-null;
// the surface isn't a physical stream surface for reprocessing request
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");
}
for (int i = 0; i < mConfiguredOutputs.size(); i++) {
OutputConfiguration configuration = mConfiguredOutputs.valueAt(i);
if (configuration.isForPhysicalCamera()
&& configuration.getSurfaces().contains(surface)) {
if (request.isReprocess()) {
throw new IllegalArgumentException(
"Reprocess request on physical stream is not allowed");
}
}
}
}
}
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
if (repeating) {
stopRepeating();
}
SubmitInfo requestInfo;
CaptureRequest[] requestArray = requestList.toArray(new CaptureRequest[requestList.size()]);
// Convert Surface to streamIdx and surfaceIdx
for (CaptureRequest request : requestArray) {
request.convertSurfaceToStreamId(mConfiguredOutputs);
}
requestInfo = mRemoteDevice.submitRequestList(requestArray, repeating);
if (DEBUG) {
Log.v(TAG, "last frame number " + requestInfo.getLastFrameNumber());
}
for (CaptureRequest request : requestArray) {
request.recoverStreamIdToSurface();
}
if (callback != null) {
mCaptureCallbackMap.put(requestInfo.getRequestId(),
new CaptureCallbackHolder(
callback, requestList, executor, repeating, mNextSessionId - 1));
} else {
if (DEBUG) {
Log.d(TAG, "Listen for request " + requestInfo.getRequestId() + " is null");
}
}
if (repeating) {
if (mRepeatingRequestId != REQUEST_ID_NONE) {
checkEarlyTriggerSequenceComplete(mRepeatingRequestId,
requestInfo.getLastFrameNumber(),
mRepeatingRequestTypes);
}
mRepeatingRequestId = requestInfo.getRequestId();
mRepeatingRequestTypes = getRequestTypes(requestArray);
} else {
mRequestLastFrameNumbersList.add(
new RequestLastFrameNumbersHolder(requestList, requestInfo));
}
if (mIdle) {
mDeviceExecutor.execute(mCallOnActive);
}
mIdle = false;
return requestInfo.getRequestId();
}
}
public int setRepeatingRequest(CaptureRequest request, CaptureCallback callback,
Executor executor) throws CameraAccessException {
List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
requestList.add(request);
return submitCaptureRequest(requestList, callback, executor, /*streaming*/true);
}
public int setRepeatingBurst(List<CaptureRequest> requests, CaptureCallback callback,
Executor executor) throws CameraAccessException {
if (requests == null || requests.isEmpty()) {
throw new IllegalArgumentException("At least one request must be given");
}
return submitCaptureRequest(requests, callback, executor, /*streaming*/true);
}
public void stopRepeating() throws CameraAccessException {
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
if (mRepeatingRequestId != REQUEST_ID_NONE) {
int requestId = mRepeatingRequestId;
mRepeatingRequestId = REQUEST_ID_NONE;
int[] requestTypes = mRepeatingRequestTypes;
mRepeatingRequestTypes = null;
long lastFrameNumber;
try {
lastFrameNumber = mRemoteDevice.cancelRequest(requestId);
} catch (IllegalArgumentException e) {
if (DEBUG) {
Log.v(TAG, "Repeating request was already stopped for request " + requestId);
}
// Repeating request was already stopped. Nothing more to do.
return;
}
checkEarlyTriggerSequenceComplete(requestId, lastFrameNumber, requestTypes);
}
}
}
private void waitUntilIdle() throws CameraAccessException {
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
if (mRepeatingRequestId != REQUEST_ID_NONE) {
throw new IllegalStateException("Active repeating request ongoing");
}
mRemoteDevice.waitUntilIdle();
}
}
public void flush() throws CameraAccessException {
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
mDeviceExecutor.execute(mCallOnBusy);
// If already idle, just do a busy->idle transition immediately, don't actually
// flush.
if (mIdle) {
mDeviceExecutor.execute(mCallOnIdle);
return;
}
long lastFrameNumber = mRemoteDevice.flush();
if (mRepeatingRequestId != REQUEST_ID_NONE) {
checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber,
mRepeatingRequestTypes);
mRepeatingRequestId = REQUEST_ID_NONE;
mRepeatingRequestTypes = null;
}
}
}
@Override
public void close() {
synchronized (mInterfaceLock) {
if (mClosing.getAndSet(true)) {
return;
}
if (mRemoteDevice != null) {
mRemoteDevice.disconnect();
mRemoteDevice.unlinkToDeath(this, /*flags*/0);
}
// 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) {
mDeviceExecutor.execute(mCallOnClosed);
}
mRemoteDevice = null;
}
}
@Override
protected void finalize() throws Throwable {
try {
close();
}
finally {
super.finalize();
}
}
private void checkInputConfiguration(InputConfiguration inputConfig) {
if (inputConfig != null) {
StreamConfigurationMap configMap = mCharacteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
int[] inputFormats = configMap.getInputFormats();
boolean validFormat = false;
for (int format : inputFormats) {
if (format == inputConfig.getFormat()) {
validFormat = true;
}
}
if (validFormat == false) {
throw new IllegalArgumentException("input format " + inputConfig.getFormat() +
" is not valid");
}
boolean validSize = false;
Size[] inputSizes = configMap.getInputSizes(inputConfig.getFormat());
for (Size s : inputSizes) {
if (inputConfig.getWidth() == s.getWidth() &&
inputConfig.getHeight() == s.getHeight()) {
validSize = true;
}
}
if (validSize == false) {
throw new IllegalArgumentException("input size " + inputConfig.getWidth() + "x" +
inputConfig.getHeight() + " is not valid");
}
}
}
/**
* <p>A callback for tracking the progress of a {@link CaptureRequest}
* submitted to the camera device.</p>
*
* An interface instead of an abstract class because this is internal and
* we want to make sure we always implement all its callbacks until we reach
* the public layer.
*/
public interface CaptureCallback {
/**
* This constant is used to indicate that no images were captured for
* the request.
*
* @hide
*/
public static final int NO_FRAMES_CAPTURED = -1;
/**
* This method is called when the camera device has started capturing
* the output image for the request, at the beginning of image exposure.
*
* @see android.media.MediaActionSound
*/
public void onCaptureStarted(CameraDevice camera,
CaptureRequest request, long timestamp, long frameNumber);
/**
* This method is called when some results from an image capture are
* available.
*
* @hide
*/
public void onCapturePartial(CameraDevice camera,
CaptureRequest request, CaptureResult result);
/**
* This method is called when an image capture makes partial forward progress; some
* (but not all) results from an image capture are available.
*
*/
public void onCaptureProgressed(CameraDevice camera,
CaptureRequest request, CaptureResult partialResult);
/**
* This method is called when an image capture has fully completed and all the
* result metadata is available.
*/
public void onCaptureCompleted(CameraDevice camera,
CaptureRequest request, TotalCaptureResult result);
/**
* This method is called instead of {@link #onCaptureCompleted} when the
* camera device failed to produce a {@link CaptureResult} for the
* request.
*/
public void onCaptureFailed(CameraDevice camera,
CaptureRequest request, CaptureFailure failure);
/**
* This method is called independently of the others in CaptureCallback,
* when a capture sequence finishes and all {@link CaptureResult}
* or {@link CaptureFailure} for it have been returned via this callback.
*/
public void onCaptureSequenceCompleted(CameraDevice camera,
int sequenceId, long frameNumber);
/**
* This method is called independently of the others in CaptureCallback,
* when a capture sequence aborts before any {@link CaptureResult}
* or {@link CaptureFailure} for it have been returned via this callback.
*/
public void onCaptureSequenceAborted(CameraDevice camera,
int sequenceId);
/**
* This method is called independently of the others in CaptureCallback, if an output buffer
* is dropped for a particular capture request.
*
* Loss of metadata is communicated via onCaptureFailed, independently of any buffer loss.
*/
public void onCaptureBufferLost(CameraDevice camera,
CaptureRequest request, Surface target, long frameNumber);
}
/**
* A callback for notifications about the state of a camera device, adding in the callbacks that
* were part of the earlier KK API design, but now only used internally.
*/
public static abstract class StateCallbackKK extends StateCallback {
/**
* The method called when a camera device has no outputs configured.
*
*/
public void onUnconfigured(CameraDevice camera) {
// Default empty implementation
}
/**
* The method called when a camera device begins processing
* {@link CaptureRequest capture requests}.
*
*/
public void onActive(CameraDevice camera) {
// Default empty implementation
}
/**
* The method called when a camera device is busy.
*
*/
public void onBusy(CameraDevice camera) {
// Default empty implementation
}
/**
* The method called when a camera device has finished processing all
* submitted capture requests and has reached an idle state.
*
*/
public void onIdle(CameraDevice camera) {
// Default empty implementation
}
/**
* This method is called when camera device's non-repeating request queue is empty,
* and is ready to start capturing next image.
*/
public void onRequestQueueEmpty() {
// Default empty implementation
}
/**
* The method called when the camera device has finished preparing
* an output Surface
*/
public void onSurfacePrepared(Surface surface) {
// Default empty implementation
}
}
static class CaptureCallbackHolder {
private final boolean mRepeating;
private final CaptureCallback mCallback;
private final List<CaptureRequest> mRequestList;
private final Executor mExecutor;
private final int mSessionId;
/**
* <p>Determine if the callback holder is for a constrained high speed request list that
* expects batched capture results. Capture results will be batched if the request list
* is interleaved with preview and video requests. Capture results won't be batched if the
* request list only contains preview requests, or if the request doesn't belong to a
* constrained high speed list.
*/
private final boolean mHasBatchedOutputs;
CaptureCallbackHolder(CaptureCallback callback, List<CaptureRequest> requestList,
Executor executor, boolean repeating, int sessionId) {
if (callback == null || executor == null) {
throw new UnsupportedOperationException(
"Must have a valid handler and a valid callback");
}
mRepeating = repeating;
mExecutor = executor;
mRequestList = new ArrayList<CaptureRequest>(requestList);
mCallback = callback;
mSessionId = sessionId;
// Check whether this callback holder is for batched outputs.
// The logic here should match createHighSpeedRequestList.
boolean hasBatchedOutputs = true;
for (int i = 0; i < requestList.size(); i++) {
CaptureRequest request = requestList.get(i);
if (!request.isPartOfCRequestList()) {
hasBatchedOutputs = false;
break;
}
if (i == 0) {
Collection<Surface> targets = request.getTargets();
if (targets.size() != 2) {
hasBatchedOutputs = false;
break;
}
}
}
mHasBatchedOutputs = hasBatchedOutputs;
}
public boolean isRepeating() {
return mRepeating;
}
public CaptureCallback getCallback() {
return mCallback;
}
public CaptureRequest getRequest(int subsequenceId) {
if (subsequenceId >= mRequestList.size()) {
throw new IllegalArgumentException(
String.format(
"Requested subsequenceId %d is larger than request list size %d.",
subsequenceId, mRequestList.size()));
} else {
if (subsequenceId < 0) {
throw new IllegalArgumentException(String.format(
"Requested subsequenceId %d is negative", subsequenceId));
} else {
return mRequestList.get(subsequenceId);
}
}
}
public CaptureRequest getRequest() {
return getRequest(0);
}
public Executor getExecutor() {
return mExecutor;
}
public int getSessionId() {
return mSessionId;
}
public int getRequestCount() {
return mRequestList.size();
}
public boolean hasBatchedOutputs() {
return mHasBatchedOutputs;
}
}
/**
* This class holds a capture ID and its expected last regular, zslStill, and reprocess
* frame number.
*/
static class RequestLastFrameNumbersHolder {
// request ID
private final int mRequestId;
// The last regular frame number for this request ID. It's
// CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no regular request.
private final long mLastRegularFrameNumber;
// The last reprocess frame number for this request ID. It's
// CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no reprocess request.
private final long mLastReprocessFrameNumber;
// The last ZSL still capture frame number for this request ID. It's
// CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no zsl request.
private final long mLastZslStillFrameNumber;
/**
* Create a request-last-frame-numbers holder with a list of requests, request ID, and
* the last frame number returned by camera service.
*/
public RequestLastFrameNumbersHolder(List<CaptureRequest> requestList, SubmitInfo requestInfo) {
long lastRegularFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
long lastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
long lastZslStillFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
long frameNumber = requestInfo.getLastFrameNumber();
if (requestInfo.getLastFrameNumber() < requestList.size() - 1) {
throw new IllegalArgumentException(
"lastFrameNumber: " + requestInfo.getLastFrameNumber() +
" should be at least " + (requestList.size() - 1) + " for the number of " +
" requests in the list: " + requestList.size());
}
// find the last regular, zslStill, and reprocess frame number
for (int i = requestList.size() - 1; i >= 0; i--) {
CaptureRequest request = requestList.get(i);
int requestType = request.getRequestType();
if (requestType == CaptureRequest.REQUEST_TYPE_REPROCESS
&& lastReprocessFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) {
lastReprocessFrameNumber = frameNumber;
} else if (requestType == CaptureRequest.REQUEST_TYPE_ZSL_STILL
&& lastZslStillFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) {
lastZslStillFrameNumber = frameNumber;
} else if (requestType == CaptureRequest.REQUEST_TYPE_REGULAR
&& lastRegularFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) {
lastRegularFrameNumber = frameNumber;
}
if (lastReprocessFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED
&& lastZslStillFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED
&& lastRegularFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED) {
break;
}
frameNumber--;
}
mLastRegularFrameNumber = lastRegularFrameNumber;
mLastReprocessFrameNumber = lastReprocessFrameNumber;
mLastZslStillFrameNumber = lastZslStillFrameNumber;
mRequestId = requestInfo.getRequestId();
}
/**
* Create a request-last-frame-numbers holder with a request ID and last regular/ZslStill
* frame number.
*/
RequestLastFrameNumbersHolder(int requestId, long lastFrameNumber,
int[] repeatingRequestTypes) {
long lastRegularFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
long lastZslStillFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
if (repeatingRequestTypes == null) {
throw new IllegalArgumentException(
"repeatingRequest list must not be null");
}
if (lastFrameNumber < repeatingRequestTypes.length - 1) {
throw new IllegalArgumentException(
"lastFrameNumber: " + lastFrameNumber + " should be at least "
+ (repeatingRequestTypes.length - 1)
+ " for the number of requests in the list: "
+ repeatingRequestTypes.length);
}
long frameNumber = lastFrameNumber;
for (int i = repeatingRequestTypes.length - 1; i >= 0; i--) {
if (repeatingRequestTypes[i] == CaptureRequest.REQUEST_TYPE_ZSL_STILL
&& lastZslStillFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) {
lastZslStillFrameNumber = frameNumber;
} else if (repeatingRequestTypes[i] == CaptureRequest.REQUEST_TYPE_REGULAR
&& lastRegularFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) {
lastRegularFrameNumber = frameNumber;
}
if (lastZslStillFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED
&& lastRegularFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED) {
break;
}
frameNumber--;
}
mLastRegularFrameNumber = lastRegularFrameNumber;
mLastZslStillFrameNumber = lastZslStillFrameNumber;
mLastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
mRequestId = requestId;
}
/**
* Return the last regular frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if
* it contains no regular request.
*/
public long getLastRegularFrameNumber() {
return mLastRegularFrameNumber;
}
/**
* Return the last reprocess frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if
* it contains no reprocess request.
*/
public long getLastReprocessFrameNumber() {
return mLastReprocessFrameNumber;
}
/**
* Return the last ZslStill frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if
* it contains no Zsl request.
*/
public long getLastZslStillFrameNumber() {
return mLastZslStillFrameNumber;
}
/**
* Return the last frame number overall.
*/
public long getLastFrameNumber() {
return Math.max(mLastZslStillFrameNumber,
Math.max(mLastRegularFrameNumber, mLastReprocessFrameNumber));
}
/**
* Return the request ID.
*/
public int getRequestId() {
return mRequestId;
}
}
/**
* This class tracks the last frame number for submitted requests.
*/
public class FrameNumberTracker {
/** the completed frame number for each type of capture results */
private long[] mCompletedFrameNumber = new long[CaptureRequest.REQUEST_TYPE_COUNT];
/** the skipped frame numbers that don't belong to each type of capture results */
private final LinkedList<Long>[] mSkippedOtherFrameNumbers =
new LinkedList[CaptureRequest.REQUEST_TYPE_COUNT];
/** the skipped frame numbers that belong to each type of capture results */
private final LinkedList<Long>[] mSkippedFrameNumbers =
new LinkedList[CaptureRequest.REQUEST_TYPE_COUNT];
/** frame number -> request type */
private final TreeMap<Long, Integer> mFutureErrorMap = new TreeMap<Long, Integer>();
/** Map frame numbers to list of partial results */
private final HashMap<Long, List<CaptureResult>> mPartialResults = new HashMap<>();
public FrameNumberTracker() {
for (int i = 0; i < CaptureRequest.REQUEST_TYPE_COUNT; i++) {
mCompletedFrameNumber[i] = CaptureCallback.NO_FRAMES_CAPTURED;
mSkippedOtherFrameNumbers[i] = new LinkedList<Long>();
mSkippedFrameNumbers[i] = new LinkedList<Long>();
}
}
private void update() {
Iterator iter = mFutureErrorMap.entrySet().iterator();
while (iter.hasNext()) {
TreeMap.Entry pair = (TreeMap.Entry)iter.next();
Long errorFrameNumber = (Long)pair.getKey();
int requestType = (int) pair.getValue();
Boolean removeError = false;
if (errorFrameNumber == mCompletedFrameNumber[requestType] + 1) {
mCompletedFrameNumber[requestType] = errorFrameNumber;
removeError = true;
} else {
if (!mSkippedFrameNumbers[requestType].isEmpty()) {
if (errorFrameNumber == mSkippedFrameNumbers[requestType].element()) {
mCompletedFrameNumber[requestType] = errorFrameNumber;
mSkippedFrameNumbers[requestType].remove();
removeError = true;
}
} else {
for (int i = 1; i < CaptureRequest.REQUEST_TYPE_COUNT; i++) {
int otherType = (requestType + i) % CaptureRequest.REQUEST_TYPE_COUNT;
if (!mSkippedOtherFrameNumbers[otherType].isEmpty() && errorFrameNumber
== mSkippedOtherFrameNumbers[otherType].element()) {
mCompletedFrameNumber[requestType] = errorFrameNumber;
mSkippedOtherFrameNumbers[otherType].remove();
removeError = true;
break;
}
}
}
}
if (removeError) {
iter.remove();
}
}
}
/**
* This function is called every time when a result or an error is received.
* @param frameNumber the frame number corresponding to the result or error
* @param isError true if it is an error, false if it is not an error
* @param requestType the type of capture request: Reprocess, ZslStill, or Regular.
*/
public void updateTracker(long frameNumber, boolean isError, int requestType) {
if (isError) {
mFutureErrorMap.put(frameNumber, requestType);
} else {
try {
updateCompletedFrameNumber(frameNumber, requestType);
} catch (IllegalArgumentException e) {
Log.e(TAG, e.getMessage());
}
}
update();
}
/**
* This function is called every time a result has been completed.
*
* <p>It keeps a track of all the partial results already created for a particular
* frame number.</p>
*
* @param frameNumber the frame number corresponding to the result
* @param result the total or partial result
* @param partial {@true} if the result is partial, {@code false} if total
* @param requestType the type of capture request: Reprocess, ZslStill, or Regular.
*/
public void updateTracker(long frameNumber, CaptureResult result, boolean partial,
int requestType) {
if (!partial) {
// Update the total result's frame status as being successful
updateTracker(frameNumber, /*isError*/false, requestType);
// Don't keep a list of total results, we don't need to track them
return;
}
if (result == null) {
// Do not record blank results; this also means there will be no total result
// so it doesn't matter that the partials were not recorded
return;
}
// Partial results must be aggregated in-order for that frame number
List<CaptureResult> partials = mPartialResults.get(frameNumber);
if (partials == null) {
partials = new ArrayList<>();
mPartialResults.put(frameNumber, partials);
}
partials.add(result);
}
/**
* Attempt to pop off all of the partial results seen so far for the {@code frameNumber}.
*
* <p>Once popped-off, the partial results are forgotten (unless {@code updateTracker}
* is called again with new partials for that frame number).</p>
*
* @param frameNumber the frame number corresponding to the result
* @return a list of partial results for that frame with at least 1 element,
* or {@code null} if there were no partials recorded for that frame
*/
public List<CaptureResult> popPartialResults(long frameNumber) {
return mPartialResults.remove(frameNumber);
}
public long getCompletedFrameNumber() {
return mCompletedFrameNumber[CaptureRequest.REQUEST_TYPE_REGULAR];
}
public long getCompletedReprocessFrameNumber() {
return mCompletedFrameNumber[CaptureRequest.REQUEST_TYPE_REPROCESS];
}
public long getCompletedZslStillFrameNumber() {
return mCompletedFrameNumber[CaptureRequest.REQUEST_TYPE_ZSL_STILL];
}
/**
* Update the completed frame number for results of 3 categories
* (Regular/Reprocess/ZslStill).
*
* It validates that all previous frames of the same category have arrived.
*
* If there is a gap since previous frame number of the same category, assume the frames in
* the gap are other categories and store them in the skipped frame number queue to check
* against when frames of those categories arrive.
*/
private void updateCompletedFrameNumber(long frameNumber,
int requestType) throws IllegalArgumentException {
if (frameNumber <= mCompletedFrameNumber[requestType]) {
throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat");
}
// Assume there are only 3 different types of capture requests.
int otherType1 = (requestType + 1) % CaptureRequest.REQUEST_TYPE_COUNT;
int otherType2 = (requestType + 2) % CaptureRequest.REQUEST_TYPE_COUNT;
long maxOtherFrameNumberSeen =
Math.max(mCompletedFrameNumber[otherType1], mCompletedFrameNumber[otherType2]);
if (frameNumber < maxOtherFrameNumberSeen) {
// if frame number is smaller than completed frame numbers of other categories,
// it must be:
// - the head of mSkippedFrameNumbers for this category, or
// - in one of other mSkippedOtherFrameNumbers
if (!mSkippedFrameNumbers[requestType].isEmpty()) {
// frame number must be head of current type of mSkippedFrameNumbers if
// mSkippedFrameNumbers isn't empty.
if (frameNumber < mSkippedFrameNumbers[requestType].element()) {
throw new IllegalArgumentException("frame number " + frameNumber
+ " is a repeat");
} else if (frameNumber > mSkippedFrameNumbers[requestType].element()) {
throw new IllegalArgumentException("frame number " + frameNumber
+ " comes out of order. Expecting "
+ mSkippedFrameNumbers[requestType].element());
}
// frame number matches the head of the skipped frame number queue.
mSkippedFrameNumbers[requestType].remove();
} else {
// frame number must be in one of the other mSkippedOtherFrameNumbers.
int index1 = mSkippedOtherFrameNumbers[otherType1].indexOf(frameNumber);
int index2 = mSkippedOtherFrameNumbers[otherType2].indexOf(frameNumber);
boolean inSkippedOther1 = index1 != -1;
boolean inSkippedOther2 = index2 != -1;
if (!(inSkippedOther1 ^ inSkippedOther2)) {
throw new IllegalArgumentException("frame number " + frameNumber
+ " is a repeat or invalid");
}
// We know the category of frame numbers in skippedOtherFrameNumbers leading up
// to the current frame number. Move them into the correct skippedFrameNumbers.
LinkedList<Long> srcList, dstList;
int index;
if (inSkippedOther1) {
srcList = mSkippedOtherFrameNumbers[otherType1];
dstList = mSkippedFrameNumbers[otherType2];
index = index1;
} else {
srcList = mSkippedOtherFrameNumbers[otherType2];
dstList = mSkippedFrameNumbers[otherType1];
index = index2;
}
for (int i = 0; i < index; i++) {
dstList.add(srcList.removeFirst());
}
// Remove current frame number from skippedOtherFrameNumbers
srcList.remove();
}
} else {
// there is a gap of unseen frame numbers which should belong to the other
// 2 categories. Put all the skipped frame numbers in the queue.
for (long i =
Math.max(maxOtherFrameNumberSeen, mCompletedFrameNumber[requestType]) + 1;
i < frameNumber; i++) {
mSkippedOtherFrameNumbers[requestType].add(i);
}
}
mCompletedFrameNumber[requestType] = frameNumber;
}
}
private void checkAndFireSequenceComplete() {
long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber();
long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber();
long completedZslStillFrameNumber = mFrameNumberTracker.getCompletedZslStillFrameNumber();
boolean isReprocess = false;
Iterator<RequestLastFrameNumbersHolder> iter = mRequestLastFrameNumbersList.iterator();
while (iter.hasNext()) {
final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next();
boolean sequenceCompleted = false;
final int requestId = requestLastFrameNumbers.getRequestId();
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) {
long lastRegularFrameNumber =
requestLastFrameNumbers.getLastRegularFrameNumber();
long lastReprocessFrameNumber =
requestLastFrameNumbers.getLastReprocessFrameNumber();
long lastZslStillFrameNumber =
requestLastFrameNumbers.getLastZslStillFrameNumber();
// check if it's okay to remove request from mCaptureCallbackMap
if (lastRegularFrameNumber <= completedFrameNumber
&& lastReprocessFrameNumber <= completedReprocessFrameNumber
&& lastZslStillFrameNumber <= completedZslStillFrameNumber) {
sequenceCompleted = true;
mCaptureCallbackMap.removeAt(index);
if (DEBUG) {
Log.v(TAG, String.format(
"Remove holder for requestId %d, because lastRegularFrame %d "
+ "is <= %d, lastReprocessFrame %d is <= %d, "
+ "lastZslStillFrame %d is <= %d", requestId,
lastRegularFrameNumber, completedFrameNumber,
lastReprocessFrameNumber, completedReprocessFrameNumber,
lastZslStillFrameNumber, completedZslStillFrameNumber));
}
}
}
}
// If no callback is registered for this requestId or sequence completed, remove it
// from the frame number->request pair because it's not needed anymore.
if (holder == null || sequenceCompleted) {
iter.remove();
}
// Call onCaptureSequenceCompleted
if (sequenceCompleted) {
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));
}
holder.getCallback().onCaptureSequenceCompleted(
CameraDeviceImpl.this,
requestId,
requestLastFrameNumbers.getLastFrameNumber());
}
}
};
final long ident = Binder.clearCallingIdentity();
try {
holder.getExecutor().execute(resultDispatch);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
}
public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
@Override
public IBinder asBinder() {
return this;
}
@Override
public void onDeviceError(final int errorCode, CaptureResultExtras resultExtras) {
if (DEBUG) {
Log.d(TAG, String.format(
"Device error received, code %d, frame number %d, request ID %d, subseq ID %d",
errorCode, resultExtras.getFrameNumber(), resultExtras.getRequestId(),
resultExtras.getSubsequenceId()));
}
synchronized(mInterfaceLock) {
if (mRemoteDevice == null) {
return; // Camera already closed
}
switch (errorCode) {
case ERROR_CAMERA_DISCONNECTED:
final long ident = Binder.clearCallingIdentity();
try {
CameraDeviceImpl.this.mDeviceExecutor.execute(mCallOnDisconnected);
} finally {
Binder.restoreCallingIdentity(ident);
}
break;
case ERROR_CAMERA_REQUEST:
case ERROR_CAMERA_RESULT:
case ERROR_CAMERA_BUFFER:
onCaptureErrorLocked(errorCode, resultExtras);
break;
case ERROR_CAMERA_DEVICE:
scheduleNotifyError(StateCallback.ERROR_CAMERA_DEVICE);
break;
case ERROR_CAMERA_DISABLED:
scheduleNotifyError(StateCallback.ERROR_CAMERA_DISABLED);
break;
default:
Log.e(TAG, "Unknown error from camera device: " + errorCode);
scheduleNotifyError(StateCallback.ERROR_CAMERA_SERVICE);
}
}
}
private void scheduleNotifyError(int code) {
mInError = true;
final long ident = Binder.clearCallingIdentity();
try {
CameraDeviceImpl.this.mDeviceExecutor.execute(obtainRunnable(
CameraDeviceCallbacks::notifyError, this, code).recycleOnUse());
} finally {
Binder.restoreCallingIdentity(ident);
}
}
private void notifyError(int code) {
if (!CameraDeviceImpl.this.isClosed()) {
mDeviceCallback.onError(CameraDeviceImpl.this, code);
}
}
@Override
public void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId) {
if (DEBUG) {
Log.d(TAG, "Repeating request error received. Last frame number is " +
lastFrameNumber);
}
synchronized(mInterfaceLock) {
// Camera is already closed or no repeating request is present.
if (mRemoteDevice == null || mRepeatingRequestId == REQUEST_ID_NONE) {
return; // Camera already closed
}
checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber,
mRepeatingRequestTypes);
// Check if there is already a new repeating request
if (mRepeatingRequestId == repeatingRequestId) {
mRepeatingRequestId = REQUEST_ID_NONE;
mRepeatingRequestTypes = null;
}
}
}
@Override
public void onDeviceIdle() {
if (DEBUG) {
Log.d(TAG, "Camera now idle");
}
synchronized(mInterfaceLock) {
if (mRemoteDevice == null) return; // Camera already closed
if (!CameraDeviceImpl.this.mIdle) {
final long ident = Binder.clearCallingIdentity();
try {
CameraDeviceImpl.this.mDeviceExecutor.execute(mCallOnIdle);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
CameraDeviceImpl.this.mIdle = true;
}
}
@Override
public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) {
int requestId = resultExtras.getRequestId();
final long frameNumber = resultExtras.getFrameNumber();
if (DEBUG) {
Log.d(TAG, "Capture started for id " + requestId + " frame number " + frameNumber);
}
final CaptureCallbackHolder holder;
synchronized(mInterfaceLock) {
if (mRemoteDevice == null) return; // Camera already closed
// Get the callback for this frame ID, if there is one
holder = CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId);
if (holder == null) {
return;
}
if (isClosed()) return;
// Dispatch capture start notice
final long ident = Binder.clearCallingIdentity();
try {
holder.getExecutor().execute(
new Runnable() {
@Override
public void run() {
if (!CameraDeviceImpl.this.isClosed()) {
final int subsequenceId = resultExtras.getSubsequenceId();
final CaptureRequest request = holder.getRequest(subsequenceId);
if (holder.hasBatchedOutputs()) {
// Send derived onCaptureStarted for requests within the
// batch
final Range<Integer> fpsRange =
request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
for (int i = 0; i < holder.getRequestCount(); i++) {
holder.getCallback().onCaptureStarted(
CameraDeviceImpl.this,
holder.getRequest(i),
timestamp - (subsequenceId - i) *
NANO_PER_SECOND/fpsRange.getUpper(),
frameNumber - (subsequenceId - i));
}
} else {
holder.getCallback().onCaptureStarted(
CameraDeviceImpl.this,
holder.getRequest(resultExtras.getSubsequenceId()),
timestamp, frameNumber);
}
}
}
});
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
@Override
public void onResultReceived(CameraMetadataNative result,
CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[])
throws RemoteException {
int requestId = resultExtras.getRequestId();
long frameNumber = resultExtras.getFrameNumber();
if (DEBUG) {
Log.v(TAG, "Received result frame " + frameNumber + " for id "
+ requestId);
}
synchronized(mInterfaceLock) {
if (mRemoteDevice == null) return; // Camera already closed
// TODO: Handle CameraCharacteristics access from CaptureResult correctly.
result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
final CaptureCallbackHolder holder =
CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId);
final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
boolean isPartialResult =
(resultExtras.getPartialResultCount() < mTotalPartialCount);
int requestType = request.getRequestType();
// Check if we have a callback for this
if (holder == null) {
if (DEBUG) {
Log.d(TAG,
"holder is null, early return at frame "
+ frameNumber);
}
mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
requestType);
return;
}
if (isClosed()) {
if (DEBUG) {
Log.d(TAG,
"camera is closed, early return at frame "
+ frameNumber);
}
mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
requestType);
return;
}
Runnable resultDispatch = null;
CaptureResult finalResult;
// Make a copy of the native metadata before it gets moved to a CaptureResult
// object.
final CameraMetadataNative resultCopy;
if (holder.hasBatchedOutputs()) {
resultCopy = new CameraMetadataNative(result);
} else {
resultCopy = null;
}
// Either send a partial result or the final capture completed result
if (isPartialResult) {
final CaptureResult resultAsCapture =
new CaptureResult(result, request, resultExtras);
// Partial result
resultDispatch = new Runnable() {
@Override
public void run() {
if (!CameraDeviceImpl.this.isClosed()) {
if (holder.hasBatchedOutputs()) {
// Send derived onCaptureProgressed for requests within
// the batch.
for (int i = 0; i < holder.getRequestCount(); i++) {
CameraMetadataNative resultLocal =
new CameraMetadataNative(resultCopy);
CaptureResult resultInBatch = new CaptureResult(
resultLocal, holder.getRequest(i), resultExtras);
holder.getCallback().onCaptureProgressed(
CameraDeviceImpl.this,
holder.getRequest(i),
resultInBatch);
}
} else {
holder.getCallback().onCaptureProgressed(
CameraDeviceImpl.this,
request,
resultAsCapture);
}
}
}
};
finalResult = resultAsCapture;
} else {
List<CaptureResult> partialResults =
mFrameNumberTracker.popPartialResults(frameNumber);
final long sensorTimestamp =
result.get(CaptureResult.SENSOR_TIMESTAMP);
final Range<Integer> fpsRange =
request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
final int subsequenceId = resultExtras.getSubsequenceId();
final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result,
request, resultExtras, partialResults, holder.getSessionId(),
physicalResults);
// Final capture result
resultDispatch = new Runnable() {
@Override
public void run() {
if (!CameraDeviceImpl.this.isClosed()){
if (holder.hasBatchedOutputs()) {
// Send derived onCaptureCompleted for requests within
// the batch.
for (int i = 0; i < holder.getRequestCount(); i++) {
resultCopy.set(CaptureResult.SENSOR_TIMESTAMP,
sensorTimestamp - (subsequenceId - i) *
NANO_PER_SECOND/fpsRange.getUpper());
CameraMetadataNative resultLocal =
new CameraMetadataNative(resultCopy);
// No logical multi-camera support for batched output mode.
TotalCaptureResult resultInBatch = new TotalCaptureResult(
resultLocal, holder.getRequest(i), resultExtras,
partialResults, holder.getSessionId(),
new PhysicalCaptureResultInfo[0]);
holder.getCallback().onCaptureCompleted(
CameraDeviceImpl.this,
holder.getRequest(i),
resultInBatch);
}
} else {
holder.getCallback().onCaptureCompleted(
CameraDeviceImpl.this,
request,
resultAsCapture);
}
}
}
};
finalResult = resultAsCapture;
}
final long ident = Binder.clearCallingIdentity();
try {
holder.getExecutor().execute(resultDispatch);
} finally {
Binder.restoreCallingIdentity(ident);
}
// Collect the partials for a total result; or mark the frame as totally completed
mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult,
requestType);
// Fire onCaptureSequenceCompleted
if (!isPartialResult) {
checkAndFireSequenceComplete();
}
}
}
@Override
public void onPrepared(int streamId) {
final OutputConfiguration output;
final StateCallbackKK sessionCallback;
if (DEBUG) {
Log.v(TAG, "Stream " + streamId + " is prepared");
}
synchronized(mInterfaceLock) {
output = mConfiguredOutputs.get(streamId);
sessionCallback = mSessionStateCallback;
}
if (sessionCallback == null) return;
if (output == null) {
Log.w(TAG, "onPrepared invoked for unknown output Surface");
return;
}
final List<Surface> surfaces = output.getSurfaces();
for (Surface surface : surfaces) {
sessionCallback.onSurfacePrepared(surface);
}
}
@Override
public void onRequestQueueEmpty() {
final StateCallbackKK sessionCallback;
if (DEBUG) {
Log.v(TAG, "Request queue becomes empty");
}
synchronized(mInterfaceLock) {
sessionCallback = mSessionStateCallback;
}
if (sessionCallback == null) return;
sessionCallback.onRequestQueueEmpty();
}
/**
* Called by onDeviceError for handling single-capture failures.
*/
private void onCaptureErrorLocked(int errorCode, CaptureResultExtras resultExtras) {
final int requestId = resultExtras.getRequestId();
final int subsequenceId = resultExtras.getSubsequenceId();
final long frameNumber = resultExtras.getFrameNumber();
final String errorPhysicalCameraId = resultExtras.getErrorPhysicalCameraId();
final CaptureCallbackHolder holder =
CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId);
final CaptureRequest request = holder.getRequest(subsequenceId);
Runnable failureDispatch = null;
if (errorCode == ERROR_CAMERA_BUFFER) {
// Because 1 stream id could map to multiple surfaces, we need to specify both
// streamId and surfaceId.
List<Surface> surfaces =
mConfiguredOutputs.get(resultExtras.getErrorStreamId()).getSurfaces();
for (Surface surface : surfaces) {
if (!request.containsTarget(surface)) {
continue;
}
if (DEBUG) {
Log.v(TAG, String.format("Lost output buffer reported for frame %d, target %s",
frameNumber, surface));
}
failureDispatch = new Runnable() {
@Override
public void run() {
if (!CameraDeviceImpl.this.isClosed()){
holder.getCallback().onCaptureBufferLost(
CameraDeviceImpl.this,
request,
surface,
frameNumber);
}
}
};
// Dispatch the failure callback
final long ident = Binder.clearCallingIdentity();
try {
holder.getExecutor().execute(failureDispatch);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
} else {
boolean mayHaveBuffers = (errorCode == ERROR_CAMERA_RESULT);
// This is only approximate - exact handling needs the camera service and HAL to
// disambiguate between request failures to due abort and due to real errors. For
// now, assume that if the session believes we're mid-abort, then the error is due
// to abort.
int reason = (mCurrentSession != null && mCurrentSession.isAborting()) ?
CaptureFailure.REASON_FLUSHED :
CaptureFailure.REASON_ERROR;
final CaptureFailure failure = new CaptureFailure(
request,
reason,
/*dropped*/ mayHaveBuffers,
requestId,
frameNumber,
errorPhysicalCameraId);
failureDispatch = new Runnable() {
@Override
public void run() {
if (!CameraDeviceImpl.this.isClosed()){
holder.getCallback().onCaptureFailed(
CameraDeviceImpl.this,
request,
failure);
}
}
};
// Fire onCaptureSequenceCompleted if appropriate
if (DEBUG) {
Log.v(TAG, String.format("got error frame %d", frameNumber));
}
mFrameNumberTracker.updateTracker(frameNumber,
/*error*/true, request.getRequestType());
checkAndFireSequenceComplete();
// Dispatch the failure callback
final long ident = Binder.clearCallingIdentity();
try {
holder.getExecutor().execute(failureDispatch);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
} // public class CameraDeviceCallbacks
/**
* A camera specific adapter {@link Executor} that posts all executed tasks onto the given
* {@link Handler}.
*
* @hide
*/
private static class CameraHandlerExecutor implements Executor {
private final Handler mHandler;
public CameraHandlerExecutor(@NonNull Handler handler) {
mHandler = Preconditions.checkNotNull(handler);
}
@Override
public void execute(Runnable command) {
// Return value of 'post()' will be ignored in order to keep the
// same camera behavior. For further details see b/74605221 .
mHandler.post(command);
}
}
/**
* Default executor management.
*
* <p>
* If executor is null, get the current thread's
* Looper to create a Executor with. If no looper exists, throw
* {@code IllegalArgumentException}.
* </p>
*/
static Executor checkExecutor(Executor executor) {
return (executor == null) ? checkAndWrapHandler(null) : executor;
}
/**
* Default executor management.
*
* <p>If the callback isn't null, check the executor, otherwise pass it through.</p>
*/
public static <T> Executor checkExecutor(Executor executor, T callback) {
return (callback != null) ? checkExecutor(executor) : executor;
}
/**
* Wrap Handler in Executor.
*
* <p>
* If handler is null, get the current thread's
* Looper to create a Executor with. If no looper exists, throw
* {@code IllegalArgumentException}.
* </p>
*/
public static Executor checkAndWrapHandler(Handler handler) {
return new CameraHandlerExecutor(checkHandler(handler));
}
/**
* Default handler management.
*
* <p>
* If handler is null, get the current thread's
* Looper to create a Handler with. If no looper exists, throw {@code IllegalArgumentException}.
* </p>
*/
static Handler checkHandler(Handler handler) {
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;
}
/**
* Default handler management, conditional on there being a callback.
*
* <p>If the callback isn't null, check the handler, otherwise pass it through.</p>
*/
static <T> Handler checkHandler(Handler handler, T callback) {
if (callback != null) {
return checkHandler(handler);
}
return handler;
}
private void checkIfCameraClosedOrInError() throws CameraAccessException {
if (mRemoteDevice == null) {
throw new IllegalStateException("CameraDevice was already closed");
}
if (mInError) {
throw new CameraAccessException(CameraAccessException.CAMERA_ERROR,
"The camera device has encountered a serious error");
}
}
/** Whether the camera device has started to close (may not yet have finished) */
private boolean isClosed() {
return mClosing.get();
}
private CameraCharacteristics getCharacteristics() {
return mCharacteristics;
}
/**
* Listener for binder death.
*
* <p> Handle binder death for ICameraDeviceUser. Trigger onError.</p>
*/
@Override
public void binderDied() {
Log.w(TAG, "CameraDevice " + mCameraId + " died unexpectedly");
if (mRemoteDevice == null) {
return; // Camera already closed
}
mInError = true;
Runnable r = new Runnable() {
@Override
public void run() {
if (!isClosed()) {
mDeviceCallback.onError(CameraDeviceImpl.this,
StateCallback.ERROR_CAMERA_SERVICE);
}
}
};
final long ident = Binder.clearCallingIdentity();
try {
CameraDeviceImpl.this.mDeviceExecutor.execute(r);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}