From 72ad6169e5a92d3b4e6c44e8e4fd3dc9d56ee0a0 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Sun, 16 Sep 2018 20:26:00 -0300 Subject: [PATCH] Add Preview control and setPreview --- MIGRATION.md | 19 +++++-- .../cameraview/EglBaseSurface.java | 1 - .../cameraview/VideoTextureEncoder.java | 3 +- .../otaliastudios/cameraview/CameraView.java | 36 +++++++++++-- .../com/otaliastudios/cameraview/Preview.java | 50 ++++++++++++++++++ cameraview/src/main/res/values/attrs.xml | 6 +++ .../cameraview/GLCameraPreview.java | 1 + .../cameraview/SurfaceCameraPreview.java | 52 +++++-------------- 8 files changed, 120 insertions(+), 48 deletions(-) create mode 100644 cameraview/src/main/options/com/otaliastudios/cameraview/Preview.java diff --git a/MIGRATION.md b/MIGRATION.md index 7d602f08..2e19567e 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -32,6 +32,19 @@ - VideoSizeSelector: added. It is needed to choose the capture size in VIDEO mode. Defaults to SizeSelectors.biggest(), but you can choose by aspect ratio or whatever. - isTakingPicture(): added on top of isTakingVideo(). - -TODO: improve gles stuff -TODO: takeVideoSnapshot \ No newline at end of file +- takeVideoSnapshot(): new api. Requires the experimental flag, API 18 or it will throw. + Respects orientation, videocodec and max duration limit. + Automatically rotates the data. Automatically crops the video. + NO audio support. + NO maxSize limit. +- New cameraPreview XML attribute lets you choose the backing preview engine (surfaceView, textureView, GlSurfaceView). + The default is GlSurfaceView and it is highly recommended that you do not change this. + +TODO: cameraPreview documentation +TODO: takeVideoSnapshot documentation +TODO: add audio to the video snapshots +TODO: create PictureRecorder interface + create FullPictureRecorder implementation that just uses camera.takePicture + create SnapshotPictureRecorder implementation that, for now, uses camera.setOneShotPreviewCallback + improve SnapshotPictureRecorder so that, if preview is GL, we catch the preview through GLES drawing + this would finally remove the RotationHelper! \ No newline at end of file diff --git a/cameraview/src/main/gles/com/otaliastudios/cameraview/EglBaseSurface.java b/cameraview/src/main/gles/com/otaliastudios/cameraview/EglBaseSurface.java index 6fd9f958..b4caa10b 100644 --- a/cameraview/src/main/gles/com/otaliastudios/cameraview/EglBaseSurface.java +++ b/cameraview/src/main/gles/com/otaliastudios/cameraview/EglBaseSurface.java @@ -195,6 +195,5 @@ class EglBaseSurface extends EglElement { } finally { if (bos != null) bos.close(); } - Log.d(TAG, "Saved " + width + "x" + height + " frame as '" + filename + "'"); } } diff --git a/cameraview/src/main/gles/com/otaliastudios/cameraview/VideoTextureEncoder.java b/cameraview/src/main/gles/com/otaliastudios/cameraview/VideoTextureEncoder.java index 5fd83b7f..c68eb1f3 100644 --- a/cameraview/src/main/gles/com/otaliastudios/cameraview/VideoTextureEncoder.java +++ b/cameraview/src/main/gles/com/otaliastudios/cameraview/VideoTextureEncoder.java @@ -142,7 +142,8 @@ class VideoTextureEncoder implements Runnable { } mEglCore = new EglCore(config.mEglContext, EglCore.FLAG_RECORDABLE); mInputWindowSurface = new EglWindowSurface(mEglCore, mVideoEncoder.getInputSurface(), true); - mInputWindowSurface.makeCurrent(); + mInputWindowSurface.makeCurrent(); // drawing will happen on the InputWindowSurface, which + // is backed by mVideoEncoder.getInputSurface() mFullScreen = new EglViewport(); mTransformationScaleX = config.mScaleX; mTransformationScaleY = config.mScaleY; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index 069bd02c..62c52fe4 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -53,6 +53,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { // Self managed parameters private boolean mPlaySounds; private HashMap mGestureMap = new HashMap<>(4); + private Preview mPreview; // Components /* for tests */ CameraCallbacks mCameraCallbacks; @@ -96,6 +97,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { // Self managed boolean playSounds = a.getBoolean(R.styleable.CameraView_cameraPlaySounds, DEFAULT_PLAY_SOUNDS); mExperimental = a.getBoolean(R.styleable.CameraView_cameraExperimental, false); + mPreview = Preview.fromValue(a.getInteger(R.styleable.CameraView_cameraPreview, Preview.DEFAULT.value())); // Camera controller params Facing facing = Facing.fromValue(a.getInteger(R.styleable.CameraView_cameraFacing, Facing.DEFAULT.value())); @@ -229,12 +231,18 @@ public class CameraView extends FrameLayout implements LifecycleObserver { } protected CameraPreview instantiatePreview(Context context, ViewGroup container) { - // TextureView is not supported without hardware acceleration. LOG.w("preview:", "isHardwareAccelerated:", isHardwareAccelerated()); - if (mExperimental) return new GLCameraPreview(context, container, null); - return isHardwareAccelerated() ? - new TextureCameraPreview(context, container, null) : - new SurfaceCameraPreview(context, container, null); + switch (mPreview) { + case SURFACE: return new SurfaceCameraPreview(context, container, null); + case TEXTURE: { + if (isHardwareAccelerated()) { // TextureView is not supported without hardware acceleration. + return new TextureCameraPreview(context, container, null); + } + } + case GL_SURFACE: default: { + return new GLCameraPreview(context, container, null); + } + } } /* for tests */ void instantiatePreview() { @@ -698,6 +706,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver { setWhiteBalance((WhiteBalance) control); } else if (control instanceof VideoCodec) { setVideoCodec((VideoCodec) control); + } else if (control instanceof Preview) { + setPreview((Preview) control); } } @@ -812,6 +822,22 @@ public class CameraView extends FrameLayout implements LifecycleObserver { mCameraController.setHdr(hdr); } + /** + * 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(Preview preview) { + mPreview = preview; + } + /** * Gets the current hdr value. diff --git a/cameraview/src/main/options/com/otaliastudios/cameraview/Preview.java b/cameraview/src/main/options/com/otaliastudios/cameraview/Preview.java new file mode 100644 index 00000000..b7c78d87 --- /dev/null +++ b/cameraview/src/main/options/com/otaliastudios/cameraview/Preview.java @@ -0,0 +1,50 @@ +package com.otaliastudios.cameraview; + + +/** + * The preview engine to be used. + * + * @see CameraView#setPreview(Preview) + */ +public enum Preview implements Control { + + /** + * Preview engine based on {@link android.view.SurfaceView}. + * Not recommended. + */ + SURFACE(0), + + /** + * Preview engine based on {@link android.view.TextureView}. + * Stable, but does not support all features (like video snapshots). + */ + TEXTURE(1), + + /** + * Preview engine based on {@link android.opengl.GLSurfaceView}. + * This is the best engine available. + */ + GL_SURFACE(2); + + final static Preview DEFAULT = GL_SURFACE; + + private int value; + + Preview(int value) { + this.value = value; + } + + int value() { + return value; + } + + static Preview fromValue(int value) { + Preview[] list = Preview.values(); + for (Preview action : list) { + if (action.value() == value) { + return action; + } + } + return null; + } +} diff --git a/cameraview/src/main/res/values/attrs.xml b/cameraview/src/main/res/values/attrs.xml index d5c0e5b7..23e2eb2d 100644 --- a/cameraview/src/main/res/values/attrs.xml +++ b/cameraview/src/main/res/values/attrs.xml @@ -106,6 +106,12 @@ + + + + + + diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/GLCameraPreview.java b/cameraview/src/main/views/com/otaliastudios/cameraview/GLCameraPreview.java index 16dbd0cc..91bd3968 100644 --- a/cameraview/src/main/views/com/otaliastudios/cameraview/GLCameraPreview.java +++ b/cameraview/src/main/views/com/otaliastudios/cameraview/GLCameraPreview.java @@ -76,6 +76,7 @@ class GLCameraPreview extends CameraPreview imple @Override public void surfaceDestroyed(SurfaceHolder holder) { dispatchOnOutputSurfaceDestroyed(); + mDispatched = false; } }); return glView; diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/SurfaceCameraPreview.java b/cameraview/src/main/views/com/otaliastudios/cameraview/SurfaceCameraPreview.java index 170be7bd..1cc8a3dc 100644 --- a/cameraview/src/main/views/com/otaliastudios/cameraview/SurfaceCameraPreview.java +++ b/cameraview/src/main/views/com/otaliastudios/cameraview/SurfaceCameraPreview.java @@ -12,41 +12,38 @@ import android.view.ViewGroup; // Currently this does NOT support cropping (e. g. the crop inside behavior), // so we return false in supportsCropping() in order to have proper measuring. // This means that CameraView is forced to be wrap_content. -class SurfaceCameraPreview extends CameraPreview { +class SurfaceCameraPreview extends CameraPreview { private final static CameraLogger LOG = CameraLogger.create(SurfaceCameraPreview.class.getSimpleName()); + private boolean mDispatched; + SurfaceCameraPreview(Context context, ViewGroup parent, SurfaceCallback callback) { super(context, parent, callback); } - private SurfaceView mSurfaceView; - @NonNull @Override - protected View onCreateView(Context context, ViewGroup parent) { + protected SurfaceView onCreateView(Context context, ViewGroup parent) { View root = LayoutInflater.from(context).inflate(R.layout.cameraview_surface_view, parent, false); parent.addView(root, 0); - mSurfaceView = root.findViewById(R.id.surface_view); - final SurfaceHolder holder = mSurfaceView.getHolder(); + SurfaceView surfaceView = root.findViewById(R.id.surface_view); + final SurfaceHolder holder = surfaceView.getHolder(); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); holder.addCallback(new SurfaceHolder.Callback() { - private boolean mFirstTime = true; - @Override public void surfaceCreated(SurfaceHolder holder) { - // Looks like this is too early to call anything. + // This is too early to call anything. // surfaceChanged is guaranteed to be called after, with exact dimensions. - LOG.i("callback:", "surfaceCreated"); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - LOG.i("callback:", "surfaceChanged", "w:", width, "h:", height, "firstTime:", mFirstTime); - if (mFirstTime) { + LOG.i("callback:", "surfaceChanged", "w:", width, "h:", height, "dispatched:", mDispatched); + if (!mDispatched) { dispatchOnOutputSurfaceAvailable(width, height); - mFirstTime = false; + mDispatched = true; } else { dispatchOnOutputSurfaceSizeChanged(width, height); } @@ -56,15 +53,15 @@ class SurfaceCameraPreview extends CameraPreview { public void surfaceDestroyed(SurfaceHolder holder) { LOG.i("callback:", "surfaceDestroyed"); dispatchOnOutputSurfaceDestroyed(); - mFirstTime = true; + mDispatched = false; } }); - return root.findViewById(R.id.surface_view_root); + return surfaceView; } @Override SurfaceHolder getOutput() { - return mSurfaceView.getHolder(); + return getView().getHolder(); } @Override @@ -72,26 +69,5 @@ class SurfaceCameraPreview extends CameraPreview { return SurfaceHolder.class; } - protected void applyCrop(float scaleX, float scaleY) { - /* float currWidth = getView().getWidth(); - float currHeight = getView().getHeight(); - float cropX = currWidth * (scaleX - 1) / 2f; - float cropY = currHeight * (scaleX - 1) / 2f; - LOG.e("applyCrop:", "currWidth:", currWidth, "currHeight:", currHeight); - LOG.e("applyCrop:", "scaleX:", scaleX, "scaleY:", scaleY); - mSurfaceView.setTop((int) -cropY); - mSurfaceView.setLeft((int) -cropX); - mSurfaceView.setBottom((int) (currHeight + cropY)); - mSurfaceView.setRight((int) (currWidth + cropX)); - LOG.e("applyCrop:", "top:", -cropY, "left:", -cropX, "bottom:", currHeight+cropY, "right:", currWidth+cropX); - mSurfaceView.requestLayout(); */ - // mSurfaceView.getLayoutParams().width = (int) (currWidth * scaleX); - // mSurfaceView.getLayoutParams().height = (int) (currHeight * scaleY); - // mSurfaceView.setTranslationY(-cropY / 2f); - // mSurfaceView.setTranslationX(-cropX / 2f); - // getView().setScrollX((int) (cropX / 2f)); - // getView().setScrollY((int) (cropY / 2f)); - // mSurfaceView.requestLayout(); - // super.applyCrop(scaleX, scaleY); - } + }