Add Preview control and setPreview

v2
Mattia Iavarone 6 years ago
parent c1e6c22c78
commit 72ad6169e5
  1. 19
      MIGRATION.md
  2. 1
      cameraview/src/main/gles/com/otaliastudios/cameraview/EglBaseSurface.java
  3. 3
      cameraview/src/main/gles/com/otaliastudios/cameraview/VideoTextureEncoder.java
  4. 36
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  5. 50
      cameraview/src/main/options/com/otaliastudios/cameraview/Preview.java
  6. 6
      cameraview/src/main/res/values/attrs.xml
  7. 1
      cameraview/src/main/views/com/otaliastudios/cameraview/GLCameraPreview.java
  8. 52
      cameraview/src/main/views/com/otaliastudios/cameraview/SurfaceCameraPreview.java

@ -32,6 +32,19 @@
- VideoSizeSelector: added. It is needed to choose the capture size in VIDEO mode. - 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. Defaults to SizeSelectors.biggest(), but you can choose by aspect ratio or whatever.
- isTakingPicture(): added on top of isTakingVideo(). - isTakingPicture(): added on top of isTakingVideo().
- takeVideoSnapshot(): new api. Requires the experimental flag, API 18 or it will throw.
TODO: improve gles stuff Respects orientation, videocodec and max duration limit.
TODO: takeVideoSnapshot 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!

@ -195,6 +195,5 @@ class EglBaseSurface extends EglElement {
} finally { } finally {
if (bos != null) bos.close(); if (bos != null) bos.close();
} }
Log.d(TAG, "Saved " + width + "x" + height + " frame as '" + filename + "'");
} }
} }

@ -142,7 +142,8 @@ class VideoTextureEncoder implements Runnable {
} }
mEglCore = new EglCore(config.mEglContext, EglCore.FLAG_RECORDABLE); mEglCore = new EglCore(config.mEglContext, EglCore.FLAG_RECORDABLE);
mInputWindowSurface = new EglWindowSurface(mEglCore, mVideoEncoder.getInputSurface(), true); 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(); mFullScreen = new EglViewport();
mTransformationScaleX = config.mScaleX; mTransformationScaleX = config.mScaleX;
mTransformationScaleY = config.mScaleY; mTransformationScaleY = config.mScaleY;

@ -53,6 +53,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
// Self managed parameters // Self managed parameters
private boolean mPlaySounds; private boolean mPlaySounds;
private HashMap<Gesture, GestureAction> mGestureMap = new HashMap<>(4); private HashMap<Gesture, GestureAction> mGestureMap = new HashMap<>(4);
private Preview mPreview;
// Components // Components
/* for tests */ CameraCallbacks mCameraCallbacks; /* for tests */ CameraCallbacks mCameraCallbacks;
@ -96,6 +97,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
// Self managed // Self managed
boolean playSounds = a.getBoolean(R.styleable.CameraView_cameraPlaySounds, DEFAULT_PLAY_SOUNDS); boolean playSounds = a.getBoolean(R.styleable.CameraView_cameraPlaySounds, DEFAULT_PLAY_SOUNDS);
mExperimental = a.getBoolean(R.styleable.CameraView_cameraExperimental, false); mExperimental = a.getBoolean(R.styleable.CameraView_cameraExperimental, false);
mPreview = Preview.fromValue(a.getInteger(R.styleable.CameraView_cameraPreview, Preview.DEFAULT.value()));
// Camera controller params // Camera controller params
Facing facing = Facing.fromValue(a.getInteger(R.styleable.CameraView_cameraFacing, Facing.DEFAULT.value())); 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) { protected CameraPreview instantiatePreview(Context context, ViewGroup container) {
// TextureView is not supported without hardware acceleration.
LOG.w("preview:", "isHardwareAccelerated:", isHardwareAccelerated()); LOG.w("preview:", "isHardwareAccelerated:", isHardwareAccelerated());
if (mExperimental) return new GLCameraPreview(context, container, null); switch (mPreview) {
return isHardwareAccelerated() ? case SURFACE: return new SurfaceCameraPreview(context, container, null);
new TextureCameraPreview(context, container, null) : case TEXTURE: {
new SurfaceCameraPreview(context, container, null); 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() { /* for tests */ void instantiatePreview() {
@ -698,6 +706,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
setWhiteBalance((WhiteBalance) control); setWhiteBalance((WhiteBalance) control);
} else if (control instanceof VideoCodec) { } else if (control instanceof VideoCodec) {
setVideoCodec((VideoCodec) control); 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); 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. * Gets the current hdr value.

@ -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;
}
}

@ -106,6 +106,12 @@
<enum name="on" value="1" /> <enum name="on" value="1" />
</attr> </attr>
<attr name="cameraPreview" format="enum">
<enum name="surface" value="0" />
<enum name="texture" value="1" />
<enum name="glSurface" value="2" />
</attr>
<attr name="cameraPlaySounds" format="boolean" /> <attr name="cameraPlaySounds" format="boolean" />
<attr name="cameraVideoMaxSize" format="float" /> <attr name="cameraVideoMaxSize" format="float" />

@ -76,6 +76,7 @@ class GLCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> imple
@Override @Override
public void surfaceDestroyed(SurfaceHolder holder) { public void surfaceDestroyed(SurfaceHolder holder) {
dispatchOnOutputSurfaceDestroyed(); dispatchOnOutputSurfaceDestroyed();
mDispatched = false;
} }
}); });
return glView; return glView;

@ -12,41 +12,38 @@ import android.view.ViewGroup;
// Currently this does NOT support cropping (e. g. the crop inside behavior), // Currently this does NOT support cropping (e. g. the crop inside behavior),
// so we return false in supportsCropping() in order to have proper measuring. // so we return false in supportsCropping() in order to have proper measuring.
// This means that CameraView is forced to be wrap_content. // This means that CameraView is forced to be wrap_content.
class SurfaceCameraPreview extends CameraPreview<View, SurfaceHolder> { class SurfaceCameraPreview extends CameraPreview<SurfaceView, SurfaceHolder> {
private final static CameraLogger LOG = CameraLogger.create(SurfaceCameraPreview.class.getSimpleName()); private final static CameraLogger LOG = CameraLogger.create(SurfaceCameraPreview.class.getSimpleName());
private boolean mDispatched;
SurfaceCameraPreview(Context context, ViewGroup parent, SurfaceCallback callback) { SurfaceCameraPreview(Context context, ViewGroup parent, SurfaceCallback callback) {
super(context, parent, callback); super(context, parent, callback);
} }
private SurfaceView mSurfaceView;
@NonNull @NonNull
@Override @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); View root = LayoutInflater.from(context).inflate(R.layout.cameraview_surface_view, parent, false);
parent.addView(root, 0); parent.addView(root, 0);
mSurfaceView = root.findViewById(R.id.surface_view); SurfaceView surfaceView = root.findViewById(R.id.surface_view);
final SurfaceHolder holder = mSurfaceView.getHolder(); final SurfaceHolder holder = surfaceView.getHolder();
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(new SurfaceHolder.Callback() { holder.addCallback(new SurfaceHolder.Callback() {
private boolean mFirstTime = true;
@Override @Override
public void surfaceCreated(SurfaceHolder holder) { 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. // surfaceChanged is guaranteed to be called after, with exact dimensions.
LOG.i("callback:", "surfaceCreated");
} }
@Override @Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
LOG.i("callback:", "surfaceChanged", "w:", width, "h:", height, "firstTime:", mFirstTime); LOG.i("callback:", "surfaceChanged", "w:", width, "h:", height, "dispatched:", mDispatched);
if (mFirstTime) { if (!mDispatched) {
dispatchOnOutputSurfaceAvailable(width, height); dispatchOnOutputSurfaceAvailable(width, height);
mFirstTime = false; mDispatched = true;
} else { } else {
dispatchOnOutputSurfaceSizeChanged(width, height); dispatchOnOutputSurfaceSizeChanged(width, height);
} }
@ -56,15 +53,15 @@ class SurfaceCameraPreview extends CameraPreview<View, SurfaceHolder> {
public void surfaceDestroyed(SurfaceHolder holder) { public void surfaceDestroyed(SurfaceHolder holder) {
LOG.i("callback:", "surfaceDestroyed"); LOG.i("callback:", "surfaceDestroyed");
dispatchOnOutputSurfaceDestroyed(); dispatchOnOutputSurfaceDestroyed();
mFirstTime = true; mDispatched = false;
} }
}); });
return root.findViewById(R.id.surface_view_root); return surfaceView;
} }
@Override @Override
SurfaceHolder getOutput() { SurfaceHolder getOutput() {
return mSurfaceView.getHolder(); return getView().getHolder();
} }
@Override @Override
@ -72,26 +69,5 @@ class SurfaceCameraPreview extends CameraPreview<View, SurfaceHolder> {
return SurfaceHolder.class; 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);
}
} }

Loading…
Cancel
Save