diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/Camera2IntegrationTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/Camera2IntegrationTest.java index a2218890..734ce470 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/Camera2IntegrationTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/Camera2IntegrationTest.java @@ -9,6 +9,7 @@ import com.otaliastudios.cameraview.controls.Engine; import com.otaliastudios.cameraview.engine.action.ActionHolder; import com.otaliastudios.cameraview.engine.action.BaseAction; +import org.junit.Test; import org.junit.runner.RunWith; import androidx.annotation.NonNull; diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java index 5bb245f0..9a7b02c2 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java @@ -51,6 +51,7 @@ import org.junit.Test; import org.mockito.ArgumentMatcher; import java.io.File; +import java.util.Collection; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -1130,6 +1131,38 @@ public abstract class CameraIntegrationTest extends } } + @Test + @Retry(emulatorOnly = true) + @SdkExclude(maxSdkVersion = 22, emulatorOnly = true) + public void testFrameProcessing_format() { + CameraOptions o = openSync(true); + Collection formats = o.getSupportedFrameProcessingFormats(); + closeSync(true); + + for (int format : formats) { + LOG.i("[TEST FRAME FORMAT]", "Testing", format, "..."); + Op op = testFrameProcessorFormat(format); + assertNotNull(op.await(DELAY)); + } + } + + @NonNull + private Op testFrameProcessorFormat(final int format) { + final Op op = new Op<>(); + camera.setFrameProcessingFormat(format); + camera.clearFrameProcessors(); + camera.addFrameProcessor(new FrameProcessor() { + @Override + public void process(@NonNull Frame frame) { + if (frame.getFormat() == format) { + op.controller().start(); + op.controller().end(true); + } + } + }); + return op; + } + //endregion //region Overlays diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/MockCameraEngine.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/MockCameraEngine.java index a5dfb7e6..aa556810 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/MockCameraEngine.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/MockCameraEngine.java @@ -135,6 +135,16 @@ public class MockCameraEngine extends CameraBaseEngine { mPictureFormat = pictureFormat; } + @Override + public void setHasFrameProcessors(boolean hasFrameProcessors) { + mHasFrameProcessors = hasFrameProcessors; + } + + @Override + public void setFrameProcessingFormat(int format) { + mFrameProcessingFormat = format; + } + @Override public void takePicture(@NonNull PictureResult.Stub stub) { super.takePicture(stub); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java index 1646f24f..cc668bce 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java @@ -767,6 +767,18 @@ public class Camera1Engine extends CameraBaseEngine implements return (ByteBufferFrameManager) super.getFrameManager(); } + @Override + public void setHasFrameProcessors(boolean hasFrameProcessors) { + // we don't care, FP is always on + mHasFrameProcessors = hasFrameProcessors; + } + + @Override + public void setFrameProcessingFormat(int format) { + // Ignore input: we only support NV21. + mFrameProcessingFormat = ImageFormat.NV21; + } + @Override public void onBufferAvailable(@NonNull byte[] buffer) { if (getState().isAtLeast(CameraState.ENGINE) diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java index af64078d..aa9fdee7 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java @@ -80,6 +80,7 @@ import java.util.concurrent.ExecutionException; public class Camera2Engine extends CameraBaseEngine implements ImageReader.OnImageAvailableListener, ActionHolder { + private static final int FRAME_PROCESSING_FORMAT = ImageFormat.NV21; @VisibleForTesting static final long METER_TIMEOUT = 2500; @@ -331,7 +332,7 @@ public class Camera2Engine extends CameraBaseEngine implements if (streamMap == null) { throw new RuntimeException("StreamConfigurationMap is null. Should not happen."); } - android.util.Size[] sizes = streamMap.getOutputSizes(getFrameProcessingImageFormat()); + android.util.Size[] sizes = streamMap.getOutputSizes(getFrameProcessingFormat()); List candidates = new ArrayList<>(sizes.length); for (android.util.Size size : sizes) { Size add = new Size(size.getWidth(), size.getHeight()); @@ -541,7 +542,7 @@ public class Camera2Engine extends CameraBaseEngine implements mFrameProcessingReader = ImageReader.newInstance( mFrameProcessingSize.getWidth(), mFrameProcessingSize.getHeight(), - getFrameProcessingImageFormat(), + mFrameProcessingFormat, 3); mFrameProcessingReader.setOnImageAvailableListener(this, null); mFrameProcessingSurface = mFrameProcessingReader.getSurface(); @@ -595,7 +596,7 @@ public class Camera2Engine extends CameraBaseEngine implements mPreview.setStreamSize(previewSizeForView.getWidth(), previewSizeForView.getHeight()); mPreview.setDrawRotation(getAngles().offset(Reference.BASE, Reference.VIEW, Axis.ABSOLUTE)); if (hasFrameProcessors()) { - getFrameManager().setUp(FRAME_PROCESSING_FORMAT, mFrameProcessingSize); + getFrameManager().setUp(mFrameProcessingFormat, mFrameProcessingSize); } LOG.i("onStartPreview:", "Starting preview."); @@ -1402,17 +1403,6 @@ public class Camera2Engine extends CameraBaseEngine implements return new ImageFrameManager(2); } - /** - * Returns the frame processing image format. - * Defaults to {@link ImageFormat#YUV_420_888}. - * Override at your own risk! - * @return format - */ - @SuppressWarnings("WeakerAccess") - protected int getFrameProcessingImageFormat() { - return ImageFormat.YUV_420_888; - } - @Override public void onImageAvailable(ImageReader reader) { LOG.v("onImageAvailable", "trying to acquire Image."); @@ -1448,13 +1438,34 @@ public class Camera2Engine extends CameraBaseEngine implements // Extremely rare case in which this was called in between startBind and // startPreview. This can cause issues. Try later. setHasFrameProcessors(hasFrameProcessors); - } else if (getState().isAtLeast(CameraState.BIND)) { - // Apply and restart. - Camera2Engine.super.setHasFrameProcessors(hasFrameProcessors); + return; + } + // Apply and restart. + mHasFrameProcessors = hasFrameProcessors; + if (getState().isAtLeast(CameraState.BIND)) { + restartBind(); + } + } + }); + } + + @Override + public void setFrameProcessingFormat(final int format) { + // Frame processing format is used both when binding and when starting the preview. + // If the value is changed between the two, the preview step can crash. + getOrchestrator().schedule("frame processing format (" + format + ")", + true, new Runnable() { + @Override + public void run() { + if (getState().isAtLeast(CameraState.BIND) && isChangingState()) { + // Extremely rare case in which this was called in between startBind and + // startPreview. This can cause issues. Try later. + setFrameProcessingFormat(format); + return; + } + mFrameProcessingFormat = format > 0 ? format : ImageFormat.YUV_420_888; + if (getState().isAtLeast(CameraState.BIND)) { restartBind(); - } else { - // Just apply. - Camera2Engine.super.setHasFrameProcessors(hasFrameProcessors); } } }); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java index eb01ba64..e2cd5ae9 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java @@ -52,6 +52,8 @@ public abstract class CameraBaseEngine extends CameraEngine { @SuppressWarnings("WeakerAccess") protected Size mCaptureSize; @SuppressWarnings("WeakerAccess") protected Size mPreviewStreamSize; @SuppressWarnings("WeakerAccess") protected Size mFrameProcessingSize; + @SuppressWarnings("WeakerAccess") protected int mFrameProcessingFormat; + @SuppressWarnings("WeakerAccess") protected boolean mHasFrameProcessors; @SuppressWarnings("WeakerAccess") protected Flash mFlash; @SuppressWarnings("WeakerAccess") protected WhiteBalance mWhiteBalance; @SuppressWarnings("WeakerAccess") protected VideoCodec mVideoCodec; @@ -77,7 +79,6 @@ public abstract class CameraBaseEngine extends CameraEngine { private int mVideoMaxDuration; private int mVideoBitRate; private int mAudioBitRate; - private boolean mHasFrameProcessors; private long mAutoFocusResetDelayMillis; private int mSnapshotMaxWidth; // in REF_VIEW like SizeSelectors private int mSnapshotMaxHeight; // in REF_VIEW like SizeSelectors @@ -284,6 +285,11 @@ public abstract class CameraBaseEngine extends CameraEngine { return mFrameProcessingMaxHeight; } + @Override + public final int getFrameProcessingFormat() { + return mFrameProcessingFormat; + } + @Override public final void setAutoFocusResetDelay(long delayMillis) { mAutoFocusResetDelayMillis = delayMillis; @@ -423,11 +429,6 @@ public abstract class CameraBaseEngine extends CameraEngine { return mPreviewFrameRate; } - @Override - public void setHasFrameProcessors(boolean hasFrameProcessors) { - mHasFrameProcessors = hasFrameProcessors; - } - @Override public final boolean hasFrameProcessors() { return mHasFrameProcessors; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java index 71b41ec4..837c7331 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java @@ -647,6 +647,9 @@ public abstract class CameraEngine implements public abstract void setFrameProcessingMaxHeight(int maxHeight); public abstract int getFrameProcessingMaxHeight(); + public abstract void setFrameProcessingFormat(int format); + public abstract int getFrameProcessingFormat(); + public abstract void setAutoFocusResetDelay(long delayMillis); public abstract long getAutoFocusResetDelay(); diff --git a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java index ea9fc224..1ee1891e 100644 --- a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java +++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java @@ -131,6 +131,8 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis new Option.OverlayInPreview(watermark), new Option.OverlayInPictureSnapshot(watermark), new Option.OverlayInVideoSnapshot(watermark), + // Frame Processing + new Option.FrameProcessingFormat(), // Other new Option.Grid(), new Option.GridColor(), new Option.UseDeviceOrientation() ); @@ -147,6 +149,8 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis false, false, false, false, true, // Watermarks false, false, true, + // Frame Processing + true, // Other false, false, true ); diff --git a/demo/src/main/java/com/otaliastudios/cameraview/demo/Option.java b/demo/src/main/java/com/otaliastudios/cameraview/demo/Option.java index 0b43ca5c..4837b52c 100644 --- a/demo/src/main/java/com/otaliastudios/cameraview/demo/Option.java +++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/Option.java @@ -554,4 +554,27 @@ public abstract class Option { } } + public static class FrameProcessingFormat extends Option { + + FrameProcessingFormat() { + super("Frame Processing Format"); + } + + @Override + public void set(@NonNull CameraView view, @NonNull Integer value) { + view.setFrameProcessingFormat(value); + } + + @NonNull + @Override + public Integer get(@NonNull CameraView view) { + return view.getFrameProcessingFormat(); + } + + @NonNull + @Override + public Collection getAll(@NonNull CameraView view, @NonNull CameraOptions options) { + return options.getSupportedFrameProcessingFormats(); + } + } }