am b77b7bab: Merge "Camera2: Implement idle callbacks" into klp-dev
* commit 'b77b7babd5da8b0e8517bfe30aaa904313926566': Camera2: Implement idle callbacks
This commit is contained in:
@@ -25,6 +25,8 @@ interface ICameraDeviceCallbacks
|
||||
* Keep up-to-date with frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h
|
||||
*/
|
||||
|
||||
oneway void notifyCallback(int msgType, int ext1, int ext2);
|
||||
oneway void onResultReceived(int frameId, in CameraMetadataNative result);
|
||||
oneway void onCameraError(int errorCode);
|
||||
oneway void onCameraIdle();
|
||||
oneway void onCaptureStarted(int requestId, long timestamp);
|
||||
oneway void onResultReceived(int requestId, in CameraMetadataNative result);
|
||||
}
|
||||
|
||||
@@ -183,13 +183,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
|
||||
|
||||
// Need a valid handler, or current thread needs to have a looper, if
|
||||
// listener is valid
|
||||
if (handler == null && listener != null) {
|
||||
Looper looper = Looper.myLooper();
|
||||
if (looper == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"No handler given, and current thread has no looper!");
|
||||
}
|
||||
handler = new Handler(looper);
|
||||
if (listener != null) {
|
||||
handler = checkHandler(handler);
|
||||
}
|
||||
|
||||
synchronized (mLock) {
|
||||
@@ -271,12 +266,16 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
|
||||
// impossible
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDeviceListener(StateListener listener, Handler handler) {
|
||||
synchronized (mLock) {
|
||||
if (listener != null) {
|
||||
handler = checkHandler(handler);
|
||||
}
|
||||
|
||||
mDeviceListener = listener;
|
||||
mDeviceHandler = handler;
|
||||
}
|
||||
@@ -365,21 +364,113 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
|
||||
|
||||
}
|
||||
|
||||
// TODO: unit tests
|
||||
public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
|
||||
|
||||
//
|
||||
// Constants below need to be kept up-to-date with
|
||||
// frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h
|
||||
//
|
||||
|
||||
//
|
||||
// Error codes for onCameraError
|
||||
//
|
||||
|
||||
/**
|
||||
* Camera has been disconnected
|
||||
*/
|
||||
static final int ERROR_CAMERA_DISCONNECTED = 0;
|
||||
|
||||
/**
|
||||
* Camera has encountered a device-level error
|
||||
* Matches CameraDevice.StateListener#ERROR_CAMERA_DEVICE
|
||||
*/
|
||||
static final int ERROR_CAMERA_DEVICE = 1;
|
||||
|
||||
/**
|
||||
* Camera has encountered a service-level error
|
||||
* Matches CameraDevice.StateListener#ERROR_CAMERA_SERVICE
|
||||
*/
|
||||
static final int ERROR_CAMERA_SERVICE = 2;
|
||||
|
||||
@Override
|
||||
public IBinder asBinder() {
|
||||
return this;
|
||||
}
|
||||
|
||||
// TODO: consider rename to onMessageReceived
|
||||
@Override
|
||||
public void notifyCallback(int msgType, int ext1, int ext2) throws RemoteException {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Got message " + msgType + " ext1: " + ext1 + " , ext2: " + ext2);
|
||||
public void onCameraError(final int errorCode) {
|
||||
synchronized (mLock) {
|
||||
if (CameraDevice.this.mDeviceListener == null) return;
|
||||
final StateListener listener = CameraDevice.this.mDeviceListener;
|
||||
Runnable r = null;
|
||||
switch (errorCode) {
|
||||
case ERROR_CAMERA_DISCONNECTED:
|
||||
r = new Runnable() {
|
||||
public void run() {
|
||||
listener.onDisconnected(CameraDevice.this);
|
||||
}
|
||||
};
|
||||
break;
|
||||
case ERROR_CAMERA_DEVICE:
|
||||
case ERROR_CAMERA_SERVICE:
|
||||
r = new Runnable() {
|
||||
public void run() {
|
||||
listener.onError(CameraDevice.this, errorCode);
|
||||
}
|
||||
};
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Unknown error from camera device: " + errorCode);
|
||||
}
|
||||
if (r != null) {
|
||||
CameraDevice.this.mDeviceHandler.post(r);
|
||||
}
|
||||
}
|
||||
// TODO implement rest
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraIdle() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Camera now idle");
|
||||
}
|
||||
synchronized (mLock) {
|
||||
if (CameraDevice.this.mDeviceListener == null) return;
|
||||
final StateListener listener = CameraDevice.this.mDeviceListener;
|
||||
Runnable r = new Runnable() {
|
||||
public void run() {
|
||||
listener.onIdle(CameraDevice.this);
|
||||
}
|
||||
};
|
||||
CameraDevice.this.mDeviceHandler.post(r);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCaptureStarted(int requestId, final long timestamp) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Capture started for id " + requestId);
|
||||
}
|
||||
final CaptureListenerHolder holder;
|
||||
|
||||
// Get the listener for this frame ID, if there is one
|
||||
synchronized (mLock) {
|
||||
holder = CameraDevice.this.mCaptureListenerMap.get(requestId);
|
||||
}
|
||||
|
||||
if (holder == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Dispatch capture start notice
|
||||
holder.getHandler().post(
|
||||
new Runnable() {
|
||||
public void run() {
|
||||
holder.getListener().onCaptureStarted(
|
||||
CameraDevice.this,
|
||||
holder.getRequest(),
|
||||
timestamp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -429,6 +520,22 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Default handler management. If handler is null, get the current thread's
|
||||
* Looper to create a Handler with. If no looper exists, throw exception.
|
||||
*/
|
||||
private 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;
|
||||
}
|
||||
|
||||
private void checkIfCameraClosed() {
|
||||
if (mRemoteDevice == null) {
|
||||
throw new IllegalStateException("CameraDevice was already closed");
|
||||
|
||||
@@ -652,7 +652,6 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
|
||||
* We use a class initializer to allow the native code to cache some field offsets
|
||||
*/
|
||||
static {
|
||||
System.loadLibrary("media_jni");
|
||||
nativeClassInit();
|
||||
|
||||
Log.v(TAG, "Shall register metadata marshalers");
|
||||
|
||||
@@ -152,11 +152,20 @@ public class CameraBinderTest extends AndroidTestCase {
|
||||
static class DummyCameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
|
||||
|
||||
@Override
|
||||
public void notifyCallback(int msgType, int ext1, int ext2) throws RemoteException {
|
||||
public void onCameraError(int errorCode) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResultReceived(int frameId, CameraMetadataNative result) throws RemoteException {
|
||||
public void onCameraIdle() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCaptureStarted(int requestId, long timestamp) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResultReceived(int frameId, CameraMetadataNative result)
|
||||
throws RemoteException {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.media.ImageReader;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.util.Log;
|
||||
@@ -40,6 +41,7 @@ import static android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW;
|
||||
import com.android.mediaframeworktest.MediaFrameworkIntegrationTestRunner;
|
||||
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class CameraDeviceBinderTest extends AndroidTestCase {
|
||||
@@ -48,6 +50,12 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
|
||||
private static int NUM_CALLBACKS_CHECKED = 10;
|
||||
// Wait for capture result timeout value: 1500ms
|
||||
private final static int WAIT_FOR_COMPLETE_TIMEOUT_MS = 1500;
|
||||
// Wait for flush timeout value: 1000ms
|
||||
private final static int WAIT_FOR_FLUSH_TIMEOUT_MS = 1000;
|
||||
// Wait for idle timeout value: 2000ms
|
||||
private final static int WAIT_FOR_IDLE_TIMEOUT_MS = 2000;
|
||||
// Wait while camera device starts working on requests
|
||||
private final static int WAIT_FOR_WORK_MS = 300;
|
||||
// Default size is VGA, which is mandatory camera supported image size by CDD.
|
||||
private static final int DEFAULT_IMAGE_WIDTH = 640;
|
||||
private static final int DEFAULT_IMAGE_HEIGHT = 480;
|
||||
@@ -77,11 +85,19 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
|
||||
public class DummyCameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
|
||||
|
||||
@Override
|
||||
public void notifyCallback(int msgType, int ext1, int ext2) throws RemoteException {
|
||||
public void onCameraError(int errorCode) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResultReceived(int frameId, CameraMetadataNative result) throws RemoteException {
|
||||
public void onCameraIdle() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCaptureStarted(int requestId, long timestamp) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResultReceived(int frameId, CameraMetadataNative result) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +106,7 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
|
||||
public boolean matches(Object obj) {
|
||||
return !((CameraMetadataNative) obj).isEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createDefaultSurface() {
|
||||
mImageReader =
|
||||
@@ -345,6 +361,60 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
|
||||
argThat(matcher));
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testCaptureStartedCallbacks() throws Exception {
|
||||
CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
|
||||
|
||||
ArgumentCaptor<Long> timestamps = ArgumentCaptor.forClass(Long.class);
|
||||
|
||||
// Test both single request and streaming request.
|
||||
int requestId1 = submitCameraRequest(request, /* streaming */false);
|
||||
verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).times(1)).onCaptureStarted(
|
||||
eq(requestId1),
|
||||
anyLong());
|
||||
|
||||
int streamingId = submitCameraRequest(request, /* streaming */true);
|
||||
verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).atLeast(NUM_CALLBACKS_CHECKED))
|
||||
.onCaptureStarted(
|
||||
eq(streamingId),
|
||||
timestamps.capture());
|
||||
|
||||
long timestamp = 0; // All timestamps should be larger than 0.
|
||||
for (Long nextTimestamp : timestamps.getAllValues()) {
|
||||
Log.v(TAG, "next t: " + nextTimestamp + " current t: " + timestamp);
|
||||
assertTrue("Captures are out of order", timestamp < nextTimestamp);
|
||||
timestamp = nextTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testIdleCallback() throws Exception {
|
||||
int status;
|
||||
CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
|
||||
|
||||
// Try streaming
|
||||
int streamingId = submitCameraRequest(request, /* streaming */true);
|
||||
|
||||
// Wait a bit to fill up the queue
|
||||
SystemClock.sleep(WAIT_FOR_WORK_MS);
|
||||
|
||||
// Cancel and make sure we eventually quiesce
|
||||
status = mCameraUser.cancelRequest(streamingId);
|
||||
|
||||
verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(1)).onCameraIdle();
|
||||
|
||||
// Submit a few capture requests
|
||||
int requestId1 = submitCameraRequest(request, /* streaming */false);
|
||||
int requestId2 = submitCameraRequest(request, /* streaming */false);
|
||||
int requestId3 = submitCameraRequest(request, /* streaming */false);
|
||||
int requestId4 = submitCameraRequest(request, /* streaming */false);
|
||||
int requestId5 = submitCameraRequest(request, /* streaming */false);
|
||||
|
||||
// And wait for more idle
|
||||
verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(2)).onCameraIdle();
|
||||
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testFlush() throws Exception {
|
||||
int status;
|
||||
@@ -367,10 +437,24 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
|
||||
int requestId4 = submitCameraRequest(request, /* streaming */false);
|
||||
int requestId5 = submitCameraRequest(request, /* streaming */false);
|
||||
|
||||
// Then flush
|
||||
// Then flush and wait for idle
|
||||
status = mCameraUser.flush();
|
||||
assertEquals(CameraBinderTestUtils.NO_ERROR, status);
|
||||
|
||||
verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(1)).onCameraIdle();
|
||||
|
||||
// Now a streaming request
|
||||
int streamingId = submitCameraRequest(request, /* streaming */true);
|
||||
|
||||
// Wait a bit to fill up the queue
|
||||
SystemClock.sleep(WAIT_FOR_WORK_MS);
|
||||
|
||||
// Then flush and wait for the idle callback
|
||||
status = mCameraUser.flush();
|
||||
assertEquals(CameraBinderTestUtils.NO_ERROR, status);
|
||||
|
||||
verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(2)).onCameraIdle();
|
||||
|
||||
// TODO: When errors are hooked up, count that errors + successful
|
||||
// requests equal to 5.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user