OSDN Git Service

Camera2: Implement idle callbacks
authorEino-Ville Talvala <etalvala@google.com>
Thu, 5 Sep 2013 16:04:08 +0000 (09:04 -0700)
committerEino-Ville Talvala <etalvala@google.com>
Thu, 3 Oct 2013 15:44:32 +0000 (15:44 +0000)
- Rework camera callback binder interface
- Connect up idle, disconnect callbacks
- A few unit tests for shutter firing and idle use

Bug: 10549462
Change-Id: I8455a8a0561e366b7edeef6b101682be2ec44d79

core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
core/java/android/hardware/camera2/impl/CameraDevice.java
core/java/android/hardware/camera2/impl/CameraMetadataNative.java
media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java

index 4054a92..02a73d6 100644 (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);
 }
index 70a6f44..463063c 100644 (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 {
+        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);
+                }
+            }
+        }
+
+        @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, "Got message " + msgType + " ext1: " + ext1 + " , ext2: " + ext2);
+                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);
             }
-            // TODO implement rest
+
+            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");
index 6d7b03e..6d04bf8 100644 (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");
index 1b7faec..d157478 100644 (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 {
         }
     }
 
index 13ce52e..43ebef4 100644 (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 =
@@ -346,6 +362,60 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
     }
 
     @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.
     }