diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java index 769423a5..2ed8c815 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java @@ -10,6 +10,8 @@ import androidx.test.filters.MediumTest; import android.view.ViewGroup; import com.otaliastudios.cameraview.controls.Audio; +import com.otaliastudios.cameraview.controls.Engine; +import com.otaliastudios.cameraview.controls.Preview; import com.otaliastudios.cameraview.engine.CameraEngine; import com.otaliastudios.cameraview.frame.Frame; import com.otaliastudios.cameraview.frame.FrameProcessor; @@ -37,7 +39,6 @@ import static org.junit.Assert.assertNull; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyFloat; import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.floatThat; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -70,14 +71,14 @@ public class CameraViewCallbacksTest extends BaseTest { @NonNull @Override - protected CameraEngine instantiateCameraController(@NonNull CameraEngine.Callback callback) { + protected CameraEngine instantiateCameraEngine(@NonNull Engine engine, @NonNull CameraEngine.Callback callback) { mockController = new MockCameraEngine(callback); return mockController; } @NonNull @Override - protected CameraPreview instantiatePreview(@NonNull Context context, @NonNull ViewGroup container) { + protected CameraPreview instantiatePreview(@NonNull Preview preview, @NonNull Context context, @NonNull ViewGroup container) { mockPreview = new MockCameraPreview(context, container); return mockPreview; } @@ -87,7 +88,7 @@ public class CameraViewCallbacksTest extends BaseTest { return true; } }; - camera.instantiatePreview(); + camera.doInstantiatePreview(); camera.addCameraListener(listener); camera.addFrameProcessor(processor); task = new Task<>(true); diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java index d41489b8..57912b7f 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java @@ -13,8 +13,10 @@ import android.view.ViewGroup; import com.otaliastudios.cameraview.controls.Audio; import com.otaliastudios.cameraview.controls.ControlParser; +import com.otaliastudios.cameraview.controls.Engine; import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.controls.Flash; +import com.otaliastudios.cameraview.controls.Preview; import com.otaliastudios.cameraview.engine.CameraEngine; import com.otaliastudios.cameraview.frame.Frame; import com.otaliastudios.cameraview.frame.FrameProcessor; @@ -40,14 +42,10 @@ import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.SizeSelector; import com.otaliastudios.cameraview.size.SizeSelectors; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.hamcrest.Matcher; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Matchers; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -77,14 +75,14 @@ public class CameraViewTest extends BaseTest { @NonNull @Override - protected CameraEngine instantiateCameraController(@NonNull CameraEngine.Callback callback) { + protected CameraEngine instantiateCameraEngine(@NonNull Engine engine, @NonNull CameraEngine.Callback callback) { mockController = spy(new MockCameraEngine(callback)); return mockController; } @NonNull @Override - protected CameraPreview instantiatePreview(@NonNull Context context, @NonNull ViewGroup container) { + protected CameraPreview instantiatePreview(@NonNull Preview preview, @NonNull Context context, @NonNull ViewGroup container) { mockPreview = spy(new MockCameraPreview(context, container)); return mockPreview; } @@ -95,7 +93,7 @@ public class CameraViewTest extends BaseTest { } }; // Instantiate preview now. - cameraView.instantiatePreview(); + cameraView.doInstantiatePreview(); } }); } diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/IntegrationTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/IntegrationTest.java index e50fc87e..adfcb093 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/IntegrationTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/IntegrationTest.java @@ -15,6 +15,7 @@ import com.otaliastudios.cameraview.PictureResult; import com.otaliastudios.cameraview.TestActivity; import com.otaliastudios.cameraview.VideoResult; import com.otaliastudios.cameraview.controls.Audio; +import com.otaliastudios.cameraview.controls.Engine; import com.otaliastudios.cameraview.controls.Flash; import com.otaliastudios.cameraview.controls.Hdr; import com.otaliastudios.cameraview.controls.Mode; @@ -87,7 +88,7 @@ public class IntegrationTest extends BaseTest { @NonNull @Override - protected CameraEngine instantiateCameraController(@NonNull CameraEngine.Callback callback) { + protected CameraEngine instantiateCameraEngine(@NonNull Engine engine, @NonNull CameraEngine.Callback callback) { controller = new Camera1Engine(callback); return controller; } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java index 2fcd1000..d71b8a0c 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java @@ -5,6 +5,7 @@ import android.hardware.Camera; import com.otaliastudios.cameraview.controls.Audio; import com.otaliastudios.cameraview.controls.Control; +import com.otaliastudios.cameraview.controls.Engine; import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.controls.Flash; import com.otaliastudios.cameraview.engine.Mapper; @@ -183,6 +184,8 @@ public class CameraOptions { return (Collection) Arrays.asList(VideoCodec.values()); } else if (controlClass.equals(WhiteBalance.class)) { return (Collection) getSupportedWhiteBalance(); + } else if (controlClass.equals(Engine.class)) { + return (Collection) Arrays.asList(Engine.values()); } // Unrecognized control. return Collections.emptyList(); @@ -311,8 +314,7 @@ public class CameraOptions { /** * Whether auto focus is supported. This means you can map gestures to - * {@link GestureAction#FOCUS} or {@link GestureAction#FOCUS_WITH_MARKER} - * and focus will be changed on tap. + * {@link GestureAction#AUTO_FOCUS} and focus will be changed on tap. * * @return whether auto focus is supported. */ diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index ae895c3c..318f01a4 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -33,6 +33,7 @@ import android.widget.FrameLayout; import com.otaliastudios.cameraview.controls.Audio; import com.otaliastudios.cameraview.controls.Control; import com.otaliastudios.cameraview.controls.ControlParser; +import com.otaliastudios.cameraview.controls.Engine; import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.controls.Flash; import com.otaliastudios.cameraview.markers.MarkerLayout; @@ -100,6 +101,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { private boolean mPlaySounds; private HashMap mGestureMap = new HashMap<>(4); private Preview mPreview; + private Engine mEngine; // Components @VisibleForTesting CameraCallbacks mCameraCallbacks; @@ -148,8 +150,9 @@ public class CameraView extends FrameLayout implements LifecycleObserver { boolean playSounds = a.getBoolean(R.styleable.CameraView_cameraPlaySounds, DEFAULT_PLAY_SOUNDS); mExperimental = a.getBoolean(R.styleable.CameraView_cameraExperimental, false); mPreview = controls.getPreview(); + mEngine = controls.getEngine(); - // Camera controller params + // Camera engine params int gridColor = a.getColor(R.styleable.CameraView_cameraGridColor, GridLinesLayout.DEFAULT_COLOR); long videoMaxSize = (long) a.getFloat(R.styleable.CameraView_cameraVideoMaxSize, 0); int videoMaxDuration = a.getInteger(R.styleable.CameraView_cameraVideoMaxDuration, 0); @@ -166,7 +169,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver { // Components mCameraCallbacks = new CameraCallbacks(); - mCameraEngine = instantiateCameraController(mCameraCallbacks); mUiHandler = new Handler(Looper.getMainLooper()); mFrameProcessorsHandler = WorkerHandler.get("FrameProcessorsWorker"); @@ -182,16 +184,20 @@ public class CameraView extends FrameLayout implements LifecycleObserver { addView(mScrollGestureLayout); addView(mMarkerLayout); + // Create the engine + doInstantiateEngine(); + // Apply self managed setPlaySounds(playSounds); + setGrid(controls.getGrid()); + setGridColor(gridColor); - // Apply camera controller params + // Apply camera engine params + // Adding new ones? See setEngine(). setFacing(controls.getFacing()); setFlash(controls.getFlash()); setMode(controls.getMode()); setWhiteBalance(controls.getWhiteBalance()); - setGrid(controls.getGrid()); - setGridColor(gridColor); setHdr(controls.getHdr()); setAudio(controls.getAudio()); setAudioBitRate(audioBitRate); @@ -220,24 +226,33 @@ public class CameraView extends FrameLayout implements LifecycleObserver { /** * Instantiates the camera engine. + * + * @param engine the engine preference * @param callback the engine callback * @return the engine */ @NonNull - protected CameraEngine instantiateCameraController(@NonNull CameraEngine.Callback callback) { - return new Camera1Engine(callback); + protected CameraEngine instantiateCameraEngine(@NonNull Engine engine, @NonNull CameraEngine.Callback callback) { + if (mExperimental && engine == Engine.CAMERA2) { + throw new RuntimeException("TODO"); + } else { + mEngine = Engine.CAMERA1; + return new Camera1Engine(callback); + } } /** * Instantiates the camera preview. + * + * @param preview current preview value * @param context a context * @param container the container * @return the preview */ @NonNull - protected CameraPreview instantiatePreview(@NonNull Context context, @NonNull ViewGroup container) { + protected CameraPreview instantiatePreview(@NonNull Preview preview, @NonNull Context context, @NonNull ViewGroup container) { LOG.w("preview:", "isHardwareAccelerated:", isHardwareAccelerated()); - switch (mPreview) { + switch (preview) { case SURFACE: return new SurfaceCameraPreview(context, container, null); case TEXTURE: { @@ -254,18 +269,22 @@ public class CameraView extends FrameLayout implements LifecycleObserver { } @VisibleForTesting - void instantiatePreview() { - mCameraPreview = instantiatePreview(getContext(), this); + void doInstantiatePreview() { + mCameraPreview = instantiatePreview(mPreview, getContext(), this); mCameraEngine.setPreview(mCameraPreview); } + private void doInstantiateEngine() { + mCameraEngine = instantiateCameraEngine(mEngine, mCameraCallbacks); + } + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (mCameraPreview == null) { // isHardwareAccelerated will return the real value only after we are // attached. That's why we instantiate the preview here. - instantiatePreview(); + doInstantiatePreview(); } if (!isInEditMode()) { mOrientationHelper.enable(getContext()); @@ -719,10 +738,64 @@ public class CameraView extends FrameLayout implements LifecycleObserver { setVideoCodec((VideoCodec) control); } else if (control instanceof Preview) { setPreview((Preview) control); + } else if (control instanceof Engine) { + setEngine((Engine) control); } } + /** + * Controls the preview engine. Should only be called + * if this CameraView was never added to any window + * (like if you created it programmatically). + * Otherwise, it has no effect. + * + * @see Preview#SURFACE + * @see Preview#TEXTURE + * @see Preview#GL_SURFACE + * + * @param preview desired preview engine + */ + public void setPreview(@NonNull Preview preview) { + mPreview = preview; + } + + + /** + * Controls the core engine. Should only be called + * if this CameraView is closed (open() was never called). + * Otherwise, it has no effect. + * + * @see Engine#CAMERA1 + * @see Engine#CAMERA2 + * + * @param engine desired engine + */ + public void setEngine(@NonNull Engine engine) { + if (!isClosed()) return; + mEngine = engine; + CameraEngine oldEngine = mCameraEngine; + doInstantiateEngine(); + if (mCameraPreview != null) mCameraEngine.setPreview(mCameraPreview); + + // Set again all parameters + setFacing(oldEngine.getFacing()); + setFlash(oldEngine.getFlash()); + setMode(oldEngine.getMode()); + setWhiteBalance(oldEngine.getWhiteBalance()); + setHdr(oldEngine.getHdr()); + setAudio(oldEngine.getAudio()); + setAudioBitRate(oldEngine.getAudioBitRate()); + setPictureSize(oldEngine.getPictureSizeSelector()); + setVideoSize(oldEngine.getVideoSizeSelector()); + setVideoCodec(oldEngine.getVideoCodec()); + setVideoMaxSize(oldEngine.getVideoMaxSize()); + setVideoMaxDuration(oldEngine.getVideoMaxDuration()); + setVideoBitRate(oldEngine.getVideoBitRate()); + setAutoFocusResetDelay(oldEngine.getAutoFocusResetDelay()); + } + + /** * Returns a {@link CameraOptions} instance holding supported options for this camera * session. This might change over time. It's better to hold a reference from @@ -854,23 +927,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver { } - /** - * Controls the preview engine. Should only be called - * if this CameraView was never added to any window - * (like if you created it programmatically). - * Otherwise, it has no effect. - * - * @see Preview#SURFACE - * @see Preview#TEXTURE - * @see Preview#GL_SURFACE - * - * @param preview desired preview engine - */ - public void setPreview(@NonNull Preview preview) { - mPreview = preview; - } - - /** * Gets the current hdr value. * @return the current hdr value diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/controls/ControlParser.java b/cameraview/src/main/java/com/otaliastudios/cameraview/controls/ControlParser.java index 5f9fa87f..c81f9549 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/controls/ControlParser.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/controls/ControlParser.java @@ -21,6 +21,7 @@ public class ControlParser { private int hdr; private int audio; private int videoCodec; + private int engine; public ControlParser(@NonNull Context context, @NonNull TypedArray array) { this.preview = array.getInteger(R.styleable.CameraView_cameraPreview, Preview.DEFAULT.value()); @@ -32,6 +33,7 @@ public class ControlParser { this.hdr = array.getInteger(R.styleable.CameraView_cameraHdr, Hdr.DEFAULT.value()); this.audio = array.getInteger(R.styleable.CameraView_cameraAudio, Audio.DEFAULT.value()); this.videoCodec = array.getInteger(R.styleable.CameraView_cameraVideoCodec, VideoCodec.DEFAULT.value()); + this.engine = array.getInteger(R.styleable.CameraView_cameraEngine, Engine.DEFAULT.value()); } public Preview getPreview() { @@ -69,4 +71,8 @@ public class ControlParser { public VideoCodec getVideoCodec() { return VideoCodec.fromValue(videoCodec); } + + public Engine getEngine() { + return Engine.fromValue(engine); + } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/controls/Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/controls/Engine.java new file mode 100644 index 00000000..c3336d4b --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/controls/Engine.java @@ -0,0 +1,48 @@ +package com.otaliastudios.cameraview.controls; + + +import com.otaliastudios.cameraview.CameraView; + +import androidx.annotation.Nullable; + +/** + * The engine to be used. + * + * @see CameraView#setEngine(Engine) + */ +public enum Engine implements Control { + + /** + * Camera1 based engine. + */ + CAMERA1(0), + + /** + * Camera2 based engine. For API versions older than 21, + * the system falls back to {@link #CAMERA1}. + */ + CAMERA2(1); + + final static Engine DEFAULT = CAMERA1; + + private int value; + + Engine(int value) { + this.value = value; + } + + int value() { + return value; + } + + @Nullable + static Engine fromValue(int value) { + Engine[] list = Engine.values(); + for (Engine action : list) { + if (action.value() == value) { + return action; + } + } + return null; + } +} 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 cc5a0ca8..b500ec68 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java @@ -494,15 +494,13 @@ public abstract class CameraEngine implements @SuppressWarnings("unused") @NonNull - @VisibleForTesting - final SizeSelector getPictureSizeSelector() { + public final SizeSelector getPictureSizeSelector() { return mPictureSizeSelector; } @SuppressWarnings("unused") @NonNull - @VisibleForTesting - final SizeSelector getVideoSizeSelector() { + public final SizeSelector getVideoSizeSelector() { return mVideoSizeSelector; } diff --git a/cameraview/src/main/res/values/attrs.xml b/cameraview/src/main/res/values/attrs.xml index eac133e8..0fe089c4 100644 --- a/cameraview/src/main/res/values/attrs.xml +++ b/cameraview/src/main/res/values/attrs.xml @@ -108,6 +108,11 @@ + + + + +