Camera2: Stop repeating request for abandoned output

Stop repeating request if any of its output stream is abandoned
and notify that repeating request has been stopped.

Update binder tests for binder interface changes.

Update CameraDeviceImpl to expect an exception when canceling
a repeating request that is already stopped.

Bug: 21270879
Change-Id: I9fa72ae7218948aac88cb1a8e57839bd022c4a5e
This commit is contained in:
Chien-Yu Chen
2016-04-14 13:33:00 -07:00
parent 95a4791d1a
commit 2da496f1ce
9 changed files with 173 additions and 11 deletions

View File

@@ -921,7 +921,16 @@ public class CameraDeviceImpl extends CameraDevice
int requestId = mRepeatingRequestId;
mRepeatingRequestId = REQUEST_ID_NONE;
long lastFrameNumber = mRemoteDevice.cancelRequest(requestId);
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);
}
@@ -1685,6 +1694,24 @@ public class CameraDeviceImpl extends CameraDevice
}
}
@Override
public void onRepeatingRequestError(long lastFrameNumber) {
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);
mRepeatingRequestId = REQUEST_ID_NONE;
}
}
@Override
public void onDeviceIdle() {
if (DEBUG) {

View File

@@ -76,6 +76,7 @@ public class CameraDeviceState {
void onBusy();
void onCaptureStarted(RequestHolder holder, long timestamp);
void onCaptureResult(CameraMetadataNative result, RequestHolder holder);
void onRepeatingRequestError(long lastFrameNumber);
}
/**
@@ -200,6 +201,22 @@ public class CameraDeviceState {
return setCaptureResult(request, result, NO_CAPTURE_ERROR, /*errorArg*/null);
}
/**
* Set repeating request error.
*
* <p>Repeating request has been stopped due to an error such as abandoned output surfaces.</p>
*
* @param lastFrameNumber Frame number of the last repeating request before it is stopped.
*/
public synchronized void setRepeatingRequestError(final long lastFrameNumber) {
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
mCurrentListener.onRepeatingRequestError(lastFrameNumber);
}
});
}
/**
* Set the listener for state transition callbacks.
*

View File

@@ -205,6 +205,7 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
private static final int CAPTURE_STARTED = 2;
private static final int RESULT_RECEIVED = 3;
private static final int PREPARED = 4;
private static final int REPEATING_REQUEST_ERROR = 5;
private final HandlerThread mHandlerThread;
private Handler mHandler;
@@ -261,6 +262,15 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
getHandler().sendMessage(msg);
}
@Override
public void onRepeatingRequestError(long lastFrameNumber) {
Message msg = getHandler().obtainMessage(REPEATING_REQUEST_ERROR,
/*arg1*/ (int) (lastFrameNumber & 0xFFFFFFFFL),
/*arg2*/ (int) ( (lastFrameNumber >> 32) & 0xFFFFFFFFL));
getHandler().sendMessage(msg);
}
@Override
public IBinder asBinder() {
// This is solely intended to be used for in-process binding.
@@ -311,6 +321,12 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
mCallbacks.onPrepared(streamId);
break;
}
case REPEATING_REQUEST_ERROR: {
long lastFrameNumber = msg.arg2 & 0xFFFFFFFFL;
lastFrameNumber = (lastFrameNumber << 32) | (msg.arg1 & 0xFFFFFFFFL);
mCallbacks.onRepeatingRequestError(lastFrameNumber);
break;
}
default:
throw new IllegalArgumentException(
"Unknown callback message " + msg.what);

View File

@@ -242,6 +242,25 @@ public class LegacyCameraDevice implements AutoCloseable {
}
});
}
@Override
public void onRepeatingRequestError(final long lastFrameNumber) {
mResultHandler.post(new Runnable() {
@Override
public void run() {
if (DEBUG) {
Log.d(TAG, "doing onRepeatingRequestError callback.");
}
try {
mDeviceCallbacks.onRepeatingRequestError(lastFrameNumber);
} catch (RemoteException e) {
throw new IllegalStateException(
"Received remote exception during onRepeatingRequestError " +
"callback: ", e);
}
}
});
}
};
private final RequestThreadManager mRequestThreadManager;
@@ -397,8 +416,15 @@ public class LegacyCameraDevice implements AutoCloseable {
"submitRequestList - Empty/null requests are not allowed");
}
List<Long> surfaceIds = (mConfiguredSurfaces == null) ? new ArrayList<Long>() :
getSurfaceIds(mConfiguredSurfaces);
List<Long> surfaceIds;
try {
surfaceIds = (mConfiguredSurfaces == null) ? new ArrayList<Long>() :
getSurfaceIds(mConfiguredSurfaces);
} catch (BufferQueueAbandonedException e) {
throw new ServiceSpecificException(BAD_VALUE,
"submitRequestList - configured surface is abandoned.");
}
// Make sure that there all requests have at least 1 surface; all surfaces are non-null
for (CaptureRequest request : requestList) {
@@ -674,12 +700,17 @@ public class LegacyCameraDevice implements AutoCloseable {
LegacyExceptionUtils.throwOnError(nativeSetSurfaceDimens(surface, width, height));
}
static long getSurfaceId(Surface surface) {
static long getSurfaceId(Surface surface) throws BufferQueueAbandonedException {
checkNotNull(surface);
return nativeGetSurfaceId(surface);
try {
return nativeGetSurfaceId(surface);
} catch (IllegalArgumentException e) {
throw new BufferQueueAbandonedException();
}
}
static List<Long> getSurfaceIds(SparseArray<Surface> surfaces) {
static List<Long> getSurfaceIds(SparseArray<Surface> surfaces)
throws BufferQueueAbandonedException {
if (surfaces == null) {
throw new NullPointerException("Null argument surfaces");
}
@@ -696,7 +727,8 @@ public class LegacyCameraDevice implements AutoCloseable {
return surfaceIds;
}
static List<Long> getSurfaceIds(Collection<Surface> surfaces) {
static List<Long> getSurfaceIds(Collection<Surface> surfaces)
throws BufferQueueAbandonedException {
if (surfaces == null) {
throw new NullPointerException("Null argument surfaces");
}
@@ -713,7 +745,13 @@ public class LegacyCameraDevice implements AutoCloseable {
}
static boolean containsSurfaceId(Surface s, Collection<Long> ids) {
long id = getSurfaceId(s);
long id = 0;
try {
id = getSurfaceId(s);
} catch (BufferQueueAbandonedException e) {
// If surface is abandoned, return false.
return false;
}
return ids.contains(id);
}

View File

@@ -40,6 +40,7 @@ public class RequestHolder {
private final int mNumJpegTargets;
private final int mNumPreviewTargets;
private volatile boolean mFailed = false;
private boolean mOutputAbandoned = false;
private final Collection<Long> mJpegSurfaceIds;
@@ -266,4 +267,17 @@ public class RequestHolder {
return mFailed;
}
/**
* Mark at least one of this request's output surfaces is abandoned.
*/
public void setOutputAbandoned() {
mOutputAbandoned = true;
}
/**
* Return if any of this request's output surface is abandoned.
*/
public boolean isOutputAbandoned() {
return mOutputAbandoned;
}
}

View File

@@ -710,6 +710,7 @@ public class RequestThreadManager {
break;
case MSG_SUBMIT_CAPTURE_REQUEST:
Handler handler = RequestThreadManager.this.mRequestThread.getHandler();
boolean anyRequestOutputAbandoned = false;
// Get the next burst from the request queue.
Pair<BurstHolder, Long> nextBurst = mRequestQueue.getNext();
@@ -910,7 +911,22 @@ public class RequestThreadManager {
if (!holder.requestFailed()) {
mDeviceState.setCaptureResult(holder, result);
}
if (holder.isOutputAbandoned()) {
anyRequestOutputAbandoned = true;
}
}
// Stop the repeating request if any of its output surfaces is abandoned.
if (anyRequestOutputAbandoned && nextBurst.first.isRepeating()) {
long lastFrameNumber = cancelRepeating(nextBurst.first.getRequestId());
if (DEBUG) {
Log.d(TAG, "Stopped repeating request. Last frame number is " +
lastFrameNumber);
}
mDeviceState.setRepeatingRequestError(lastFrameNumber);
}
if (DEBUG) {
long totalTime = SystemClock.elapsedRealtimeNanos() - startTime;
Log.d(TAG, "Capture request took " + totalTime + " ns");

View File

@@ -525,9 +525,16 @@ public class SurfaceTextureRenderer {
checkEglError("makeCurrent");
}
private boolean swapBuffers(EGLSurface surface) {
private boolean swapBuffers(EGLSurface surface)
throws LegacyExceptionUtils.BufferQueueAbandonedException {
boolean result = EGL14.eglSwapBuffers(mEGLDisplay, surface);
checkEglError("swapBuffers");
int error = EGL14.eglGetError();
if (error == EGL14.EGL_BAD_SURFACE) {
throw new LegacyExceptionUtils.BufferQueueAbandonedException();
} else if (error != EGL14.EGL_SUCCESS) {
throw new IllegalStateException("swapBuffers: EGL error: 0x" +
Integer.toHexString(error));
}
return result;
}
@@ -722,7 +729,14 @@ public class SurfaceTextureRenderer {
addGlTimestamp(timestamp);
}
List<Long> targetSurfaceIds = LegacyCameraDevice.getSurfaceIds(targetSurfaces);
List<Long> targetSurfaceIds = new ArrayList();
try {
targetSurfaceIds = LegacyCameraDevice.getSurfaceIds(targetSurfaces);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, dropping frame. ", e);
request.setOutputAbandoned();
}
for (EGLSurfaceHolder holder : mSurfaces) {
if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
try{
@@ -737,6 +751,7 @@ public class SurfaceTextureRenderer {
swapBuffers(holder.eglSurface);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, dropping frame. ", e);
request.setOutputAbandoned();
}
}
}
@@ -761,6 +776,7 @@ public class SurfaceTextureRenderer {
holder.width, holder.height, format);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
Log.w(TAG, "Surface abandoned, dropping frame. ", e);
request.setOutputAbandoned();
}
}
}

View File

@@ -254,6 +254,15 @@ public class CameraBinderTest extends AndroidTestCase {
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
* @see android.hardware.camera2.ICameraDeviceCallbacks#onRepeatingRequestError()
*/
@Override
public void onRepeatingRequestError(long lastFrameNumber) {
// TODO Auto-generated method stub
}
}
@SmallTest

View File

@@ -145,6 +145,15 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
* @see android.hardware.camera2.ICameraDeviceCallbacks#onRepeatingRequestError()
*/
@Override
public void onRepeatingRequestError(long lastFrameNumber) {
// TODO Auto-generated method stub
}
}
class IsMetadataNotEmpty extends ArgumentMatcher<CameraMetadataNative> {