am b77b7bab: Merge "Camera2: Implement idle callbacks" into klp-dev

* commit 'b77b7babd5da8b0e8517bfe30aaa904313926566':
  Camera2: Implement idle callbacks
This commit is contained in:
Eino-Ville Talvala
2013-10-03 12:20:24 -07:00
committed by Android Git Automerger
5 changed files with 224 additions and 23 deletions

View File

@@ -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);
}

View File

@@ -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");

View File

@@ -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");

View File

@@ -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 {
}
}

View File

@@ -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.
}