From 7a213b6b973a080a831e360c27efaded83aa6bfd Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Fri, 27 Oct 2017 17:19:02 +0200 Subject: [PATCH] Implement callback dispatcher --- .../cameraview/CameraCallbacksTest.java | 12 ++++ .../cameraview/CameraViewTest.java | 45 ++++++++++++++ .../otaliastudios/cameraview/CameraView.java | 60 ++++++++++++++++++- .../cameraview/FrameProcessor.java | 2 +- 4 files changed, 117 insertions(+), 2 deletions(-) diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraCallbacksTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraCallbacksTest.java index f735fbe9..ff78f837 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraCallbacksTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraCallbacksTest.java @@ -38,6 +38,7 @@ public class CameraCallbacksTest extends BaseTest { private CameraView camera; private CameraListener listener; + private FrameProcessor processor; private MockCameraController mockController; private MockCameraPreview mockPreview; private Task task; @@ -50,6 +51,7 @@ public class CameraCallbacksTest extends BaseTest { public void run() { Context context = context(); listener = mock(CameraListener.class); + processor = mock(FrameProcessor.class); camera = new CameraView(context) { @Override protected CameraController instantiateCameraController(CameraCallbacks callbacks) { @@ -70,6 +72,7 @@ public class CameraCallbacksTest extends BaseTest { }; camera.instantiatePreview(); camera.addCameraListener(listener); + camera.addFrameProcessor(processor); task = new Task<>(); task.listen(); } @@ -294,4 +297,13 @@ public class CameraCallbacksTest extends BaseTest { Bitmap bitmap = BitmapFactory.decodeByteArray(result, 0, result.length); return new int[]{ bitmap.getWidth(), bitmap.getHeight() }; } + + @Test + public void testProcessFrame() { + completeTask().when(processor).process(any(Frame.class)); + camera.mCameraCallbacks.dispatchFrame(new byte[]{0, 1, 2, 3}, 1000, 90); + + assertNotNull(task.await(200)); + verify(processor, times(1)).process(any(Frame.class)); + } } diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java index ed987fa6..3f274413 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java @@ -3,6 +3,7 @@ package com.otaliastudios.cameraview; import android.content.Context; import android.location.Location; +import android.support.annotation.NonNull; import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; import android.view.MotionEvent; @@ -551,5 +552,49 @@ public class CameraViewTest extends BaseTest { //endregion + //region Lists of listeners and processors + + @Test + public void testCameraListenerList() { + assertTrue(cameraView.mListeners.isEmpty()); + + CameraListener listener = new CameraListener() {}; + cameraView.addCameraListener(listener); + assertEquals(cameraView.mListeners.size(), 1); + + cameraView.removeCameraListener(listener); + assertEquals(cameraView.mListeners.size(), 0); + + cameraView.addCameraListener(listener); + cameraView.addCameraListener(listener); + assertEquals(cameraView.mListeners.size(), 2); + + cameraView.clearCameraListeners(); + assertTrue(cameraView.mListeners.isEmpty()); + } + + @Test + public void testFrameProcessorsList() { + assertTrue(cameraView.mFrameProcessors.isEmpty()); + + FrameProcessor processor = new FrameProcessor() { + public void process(@NonNull Frame frame) {} + }; + cameraView.addFrameProcessor(processor); + assertEquals(cameraView.mFrameProcessors.size(), 1); + + cameraView.removeFrameProcessor(processor); + assertEquals(cameraView.mFrameProcessors.size(), 0); + + cameraView.addFrameProcessor(processor); + cameraView.addFrameProcessor(processor); + assertEquals(cameraView.mFrameProcessors.size(), 2); + + cameraView.clearFrameProcessors(); + assertTrue(cameraView.mFrameProcessors.isEmpty()); + } + + //endregion + // TODO: test permissions } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index 38252e2c..77d677f2 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -61,8 +61,9 @@ public class CameraView extends FrameLayout { private CameraPreview mCameraPreview; private OrientationHelper mOrientationHelper; private CameraController mCameraController; - private ArrayList mListeners = new ArrayList<>(2); private MediaActionSound mSound; + /* for tests */ ArrayList mListeners = new ArrayList<>(2); + /* for tests */ ArrayList mFrameProcessors = new ArrayList<>(1); // Views GridLinesLayout mGridLinesLayout; @@ -74,6 +75,7 @@ public class CameraView extends FrameLayout { // Threading private Handler mUiHandler; private WorkerHandler mWorkerHandler; + private WorkerHandler mFrameProcessorsHandler; public CameraView(@NonNull Context context) { super(context, null); @@ -119,6 +121,7 @@ public class CameraView extends FrameLayout { mCameraController = instantiateCameraController(mCameraCallbacks); mUiHandler = new Handler(Looper.getMainLooper()); mWorkerHandler = WorkerHandler.get("CameraViewWorker"); + mFrameProcessorsHandler = WorkerHandler.get("FrameProcessorsWorker"); // Views mGridLinesLayout = new GridLinesLayout(context); @@ -1092,6 +1095,40 @@ public class CameraView extends FrameLayout { } + /** + * Adds a {@link FrameProcessor} instance to be notified of + * new frames in the preview stream. + * + * @param processor a frame processor. + */ + public void addFrameProcessor(FrameProcessor processor) { + if (processor != null) { + mFrameProcessors.add(processor); + } + } + + + /** + * Remove a {@link FrameProcessor} that was previously registered. + * + * @param processor a frame processor + */ + public void removeFrameProcessor(FrameProcessor processor) { + if (processor != null) { + mFrameProcessors.remove(processor); + } + } + + + /** + * Clears the list of {@link FrameProcessor} that have been registered + * to preview frames. + */ + public void clearFrameProcessors() { + mFrameProcessors.clear(); + } + + /** * Asks the camera to capture an image of the current scene. * This will trigger {@link CameraListener#onPictureTaken(byte[])} if a listener @@ -1310,6 +1347,7 @@ public class CameraView extends FrameLayout { void dispatchOnFocusEnd(@Nullable Gesture trigger, boolean success, PointF where); void dispatchOnZoomChanged(final float newValue, final PointF[] fingers); void dispatchOnExposureCorrectionChanged(float newValue, float[] bounds, PointF[] fingers); + void dispatchFrame(byte[] frame, long time, int rotation); } private class Callbacks implements CameraCallbacks { @@ -1321,6 +1359,9 @@ public class CameraView extends FrameLayout { private Integer mDisplayOffset; private Integer mDeviceOrientation; + // Frame processing + private Frame mFrame; + Callbacks() {} @Override @@ -1557,6 +1598,23 @@ public class CameraView extends FrameLayout { } }); } + + @Override + public void dispatchFrame(final byte[] frame, final long time, final int rotation) { + mLogger.i("dispatchFrame", time, rotation, "processors:", mFrameProcessors.size()); + if (mFrameProcessors.isEmpty()) return; + if (mFrame == null) mFrame = new Frame(); + mFrameProcessorsHandler.post(new Runnable() { + @Override + public void run() { + mFrame.set(frame, time, rotation); + for (FrameProcessor processor : mFrameProcessors) { + processor.process(mFrame); + } + } + }); + + } } //endregion diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/FrameProcessor.java b/cameraview/src/main/java/com/otaliastudios/cameraview/FrameProcessor.java index 0a3110b6..c3db1342 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/FrameProcessor.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/FrameProcessor.java @@ -5,7 +5,7 @@ import android.support.annotation.WorkerThread; /** * A FrameProcessor will process {@link Frame}s coming from the camera preview. - * It must be passed to {@link Cameraview#setFrameProcessor(FrameProcessor)}. + * It must be passed to {@link CameraView#addFrameProcessor(FrameProcessor)}. */ public interface FrameProcessor {