From ec6e799adee4b56ed6434943384772726f079339 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Mon, 16 Dec 2019 11:31:01 +0100 Subject: [PATCH] Add CameraView.setFrameProcessingFormat(int), tests, docs --- .github/workflows/build.yml | 2 +- README.md | 1 + .../cameraview/CameraViewTest.java | 28 ++++++++++---- .../otaliastudios/cameraview/CameraView.java | 32 +++++++++++++++- cameraview/src/main/res/values/attrs.xml | 1 + docs/_posts/2018-12-20-frame-processing.md | 38 +++++++++++++++++-- 6 files changed, 87 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7e47af51..f4543c7a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,7 +64,7 @@ jobs: with: java-version: 1.8 - name: Execute emulator tests - timeout-minutes: 20 + timeout-minutes: 30 uses: reactivecircus/android-emulator-runner@v2.2.0 with: api-level: ${{ matrix.EMULATOR_API }} diff --git a/README.md b/README.md index d02ee871..ec06f86b 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,7 @@ Using CameraView is extremely simple: app:cameraSnapshotMaxHeight="@integer/snapshot_max_height" app:cameraFrameProcessingMaxWidth="@integer/processing_max_width" app:cameraFrameProcessingMaxHeight="@integer/processing_max_height" + app:cameraFrameProcessingFormat="@integer/processing_format" app:cameraVideoBitRate="@integer/video_bit_rate" app:cameraAudioBitRate="@integer/audio_bit_rate" app:cameraGestureTap="none|autoFocus|takePicture" diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java index c1a251d8..1bc19bed 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.content.res.TypedArray; +import android.graphics.ImageFormat; import android.graphics.PointF; import android.location.Location; import androidx.annotation.NonNull; @@ -168,10 +169,13 @@ public class CameraViewTest extends BaseTest { assertEquals(cameraView.getLocation(), null); assertEquals(cameraView.getExposureCorrection(), 0f, 0f); assertEquals(cameraView.getZoom(), 0f, 0f); - assertEquals(cameraView.getVideoMaxDuration(), 0, 0); - assertEquals(cameraView.getVideoMaxSize(), 0, 0); - assertEquals(cameraView.getSnapshotMaxWidth(), 0, 0); - assertEquals(cameraView.getSnapshotMaxHeight(), 0, 0); + assertEquals(cameraView.getVideoMaxDuration(), 0); + assertEquals(cameraView.getVideoMaxSize(), 0); + assertEquals(cameraView.getSnapshotMaxWidth(), 0); + assertEquals(cameraView.getSnapshotMaxHeight(), 0); + assertEquals(cameraView.getFrameProcessingMaxWidth(), 0); + assertEquals(cameraView.getFrameProcessingMaxHeight(), 0); + assertEquals(cameraView.getFrameProcessingFormat(), 0); // Self managed GestureParser gestures = new GestureParser(empty); @@ -806,17 +810,25 @@ public class CameraViewTest extends BaseTest { @Test public void testSnapshotMaxSize() { cameraView.setSnapshotMaxWidth(500); - assertEquals(cameraView.getSnapshotMaxWidth(), 500); + assertEquals(500, cameraView.getSnapshotMaxWidth()); cameraView.setSnapshotMaxHeight(700); - assertEquals(cameraView.getSnapshotMaxHeight(), 700); + assertEquals(700, cameraView.getSnapshotMaxHeight()); } @Test public void testFrameProcessingMaxSize() { cameraView.setFrameProcessingMaxWidth(500); - assertEquals(cameraView.getFrameProcessingMaxWidth(), 500); + assertEquals(500, cameraView.getFrameProcessingMaxWidth()); cameraView.setFrameProcessingMaxHeight(700); - assertEquals(cameraView.getFrameProcessingMaxHeight(), 700); + assertEquals(700, cameraView.getFrameProcessingMaxHeight()); + } + + @Test + public void testFrameProcessingFormat() { + cameraView.setFrameProcessingFormat(ImageFormat.YUV_420_888); + assertEquals(ImageFormat.YUV_420_888, cameraView.getFrameProcessingFormat()); + cameraView.setFrameProcessingFormat(ImageFormat.YUV_422_888); + assertEquals(ImageFormat.YUV_422_888, cameraView.getFrameProcessingFormat()); } //endregion diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index ea896030..c5bd6668 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -203,6 +203,9 @@ public class CameraView extends FrameLayout implements LifecycleObserver { DEFAULT_PICTURE_SNAPSHOT_METERING); int snapshotMaxWidth = a.getInteger(R.styleable.CameraView_cameraSnapshotMaxWidth, 0); int snapshotMaxHeight = a.getInteger(R.styleable.CameraView_cameraSnapshotMaxHeight, 0); + int frameMaxWidth = a.getInteger(R.styleable.CameraView_cameraFrameProcessingMaxWidth, 0); + int frameMaxHeight = a.getInteger(R.styleable.CameraView_cameraFrameProcessingMaxHeight, 0); + int frameFormat = a.getInteger(R.styleable.CameraView_cameraFrameProcessingFormat, 0); // Size selectors and gestures SizeSelectorParser sizeSelectors = new SizeSelectorParser(a); @@ -261,6 +264,9 @@ public class CameraView extends FrameLayout implements LifecycleObserver { setPreviewFrameRate(videoFrameRate); setSnapshotMaxWidth(snapshotMaxWidth); setSnapshotMaxHeight(snapshotMaxHeight); + setFrameProcessingMaxWidth(frameMaxWidth); + setFrameProcessingMaxHeight(frameMaxHeight); + setFrameProcessingFormat(frameFormat); // Apply gestures mapGesture(Gesture.TAP, gestures.getTapAction()); @@ -972,6 +978,9 @@ public class CameraView extends FrameLayout implements LifecycleObserver { setPreviewFrameRate(oldEngine.getPreviewFrameRate()); setSnapshotMaxWidth(oldEngine.getSnapshotMaxWidth()); setSnapshotMaxHeight(oldEngine.getSnapshotMaxHeight()); + setFrameProcessingMaxWidth(oldEngine.getFrameProcessingMaxWidth()); + setFrameProcessingMaxHeight(oldEngine.getFrameProcessingMaxHeight()); + setFrameProcessingFormat(oldEngine.getFrameProcessingFormat()); } /** @@ -2303,7 +2312,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { /** * Sets the max width for frame processing {@link Frame}s. - * This option is only supported by {@link Camera2Engine} and will have no effect + * This option is only supported by {@link Engine#CAMERA2} and will have no effect * on other engines. * * @param maxWidth max width for frames @@ -2314,7 +2323,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { /** * Sets the max height for frame processing {@link Frame}s. - * This option is only supported by {@link Camera2Engine} and will have no effect + * This option is only supported by {@link Engine#CAMERA2} and will have no effect * on other engines. * * @param maxHeight max height for frames @@ -2341,6 +2350,25 @@ public class CameraView extends FrameLayout implements LifecycleObserver { return mCameraEngine.getFrameProcessingMaxHeight(); } + /** + * Sets the {@link android.graphics.ImageFormat} for frame processing. + * Before applying you should check {@link CameraOptions#getSupportedFrameProcessingFormats()}. + * + * @param format image format + */ + public void setFrameProcessingFormat(int format) { + mCameraEngine.setFrameProcessingFormat(format); + } + + /** + * Returns the current frame processing format. + * @see #setFrameProcessingFormat(int) + * @return image format + */ + public int getFrameProcessingFormat() { + return mCameraEngine.getFrameProcessingFormat(); + } + //endregion //region Overlays diff --git a/cameraview/src/main/res/values/attrs.xml b/cameraview/src/main/res/values/attrs.xml index 20361365..ba905f08 100644 --- a/cameraview/src/main/res/values/attrs.xml +++ b/cameraview/src/main/res/values/attrs.xml @@ -27,6 +27,7 @@ + diff --git a/docs/_posts/2018-12-20-frame-processing.md b/docs/_posts/2018-12-20-frame-processing.md index e79445c4..6c64e197 100644 --- a/docs/_posts/2018-12-20-frame-processing.md +++ b/docs/_posts/2018-12-20-frame-processing.md @@ -19,12 +19,17 @@ cameraView.addFrameProcessor(new FrameProcessor() { @Override @WorkerThread public void process(@NonNull Frame frame) { - byte[] data = frame.getData(); int rotation = frame.getRotation(); long time = frame.getTime(); Size size = frame.getSize(); int format = frame.getFormat(); - // Process... + if (frame.getDataClass() == byte[].class) { + byte[] data = frame.getData(); + // Process byte array... + } else if (frame.getDataClass() == Image.class) { + Image data = frame.getData(); + // Process android.media.Image... + } } } ``` @@ -79,6 +84,15 @@ public void process(@NonNull Frame frame) { } ``` +### Frame Data + +Starting from `v2.5.0`, the type of data offered by `frame.getData()` depends on the camera engine +that created this frame: +- The Camera1 engine will offer `byte[]` arrays +- The Camera2 engine will offer `android.media.Image` objects + +You can check this at runtime by inspecting the data class using `frame.getDataClass()`. + ### Frame Size The Camera2 engine offers the option to set size constraints for the incoming frames. @@ -90,12 +104,25 @@ cameraView.setFrameProcessingMaxHeight(maxWidth); With other engines, these API have no effect. +### Frame Format + +The Camera2 engine offers the option to set the frame format as one of the ImageFormat +constants. The default is `ImageFormat.YUV_420_888`. + +```java +cameraView.setFrameProcessingFormat(ImageFormat.YUV_420_888); +cameraView.setFrameProcessingFormat(ImageFormat.YUV_422_888); +``` + +With the Camera1 engine, the incoming format will always be `ImageFormat.NV21`. + ### XML Attributes ```xml + app:cameraFrameProcessingMaxHeight="640" + app:cameraFrameProcessingFormat="0x23"/> ``` ### Related APIs @@ -109,7 +136,10 @@ With other engines, these API have no effect. |`camera.setFrameProcessingMaxHeight(int)`|`-`|Sets the max height for incoming frames.| |`camera.getFrameProcessingMaxWidth()`|`int`|Gets the max width for incoming frames.| |`camera.getFrameProcessingMaxHeight()`|`int`|Gets the max height for incoming frames.| -|`frame.getData()`|`byte[]`|The current preview frame, in its original orientation.| +|`camera.setFrameProcessingFormat(int)`|`-`|Sets the desired format for incoming frames. Should be one of the ImageFormat constants.| +|`camera.getFrameProcessingFormat()`|`-`|Gets the format for incoming frames. One of the ImageFormat constants.| +|`frame.getDataClass()`|`Class`|The class of the data returned by `getData()`. Either `byte[]` or `android.media.Image`.| +|`frame.getData()`|`T`|The current preview frame, in its original orientation.| |`frame.getTime()`|`long`|The preview timestamp, in `System.currentTimeMillis()` reference.| |`frame.getRotation()`|`int`|The rotation that should be applied to the byte array in order to see what the user sees.| |`frame.getSize()`|`Size`|The frame size, before any rotation is applied, to access data.|