Refactor preview package

pull/482/head
Mattia Iavarone 6 years ago
parent 75b417b88f
commit 3815d317bd
  1. 12
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java
  2. 5
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotPictureRecorder.java
  3. 180
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/CameraPreview.java
  4. 85
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java
  5. 32
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/RendererFrameCallback.java
  6. 3
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/RendererThread.java
  7. 16
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/SurfaceCameraPreview.java
  8. 22
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/TextureCameraPreview.java
  9. 3
      cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java

@ -11,7 +11,7 @@ import java.nio.FloatBuffer;
/** /**
* This is a mix of 3 grafika classes, FullFrameRect, Texture2dProgram, Drawable2d. * This is a mix of 3 grafika classes, FullFrameRect, Texture2dProgram, Drawable2d.
*/ */
class EglViewport extends EglElement { public class EglViewport extends EglElement {
private final static CameraLogger LOG = CameraLogger.create(EglViewport.class.getSimpleName()); private final static CameraLogger LOG = CameraLogger.create(EglViewport.class.getSimpleName());
@ -74,7 +74,7 @@ class EglViewport extends EglElement {
// private int muTexOffsetLoc; // Used for filtering // private int muTexOffsetLoc; // Used for filtering
// private int muColorAdjustLoc; // Used for filtering // private int muColorAdjustLoc; // Used for filtering
EglViewport() { public EglViewport() {
mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
mProgramHandle = createProgram(SIMPLE_VERTEX_SHADER, SIMPLE_FRAGMENT_SHADER); mProgramHandle = createProgram(SIMPLE_VERTEX_SHADER, SIMPLE_FRAGMENT_SHADER);
maPositionLocation = GLES20.glGetAttribLocation(mProgramHandle, "aPosition"); maPositionLocation = GLES20.glGetAttribLocation(mProgramHandle, "aPosition");
@ -90,16 +90,16 @@ class EglViewport extends EglElement {
} }
void release(boolean doEglCleanup) { public void release(boolean doEglCleanup) {
if (doEglCleanup) GLES20.glDeleteProgram(mProgramHandle); if (doEglCleanup) GLES20.glDeleteProgram(mProgramHandle);
mProgramHandle = -1; mProgramHandle = -1;
} }
void release() { public void release() {
release(true); release(true);
} }
int createTexture() { public int createTexture() {
int[] textures = new int[1]; int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0); GLES20.glGenTextures(1, textures, 0);
check("glGenTextures"); check("glGenTextures");
@ -117,7 +117,7 @@ class EglViewport extends EglElement {
return texId; return texId;
} }
void drawFrame(int textureId, float[] textureMatrix) { public void drawFrame(int textureId, float[] textureMatrix) {
drawFrame(textureId, textureMatrix, drawFrame(textureId, textureMatrix,
mVertexCoordinatesArray, mVertexCoordinatesArray,
mTextureCoordinatesArray); mTextureCoordinatesArray);

@ -25,6 +25,7 @@ import com.otaliastudios.cameraview.internal.utils.RotationHelper;
import com.otaliastudios.cameraview.internal.utils.WorkerHandler; import com.otaliastudios.cameraview.internal.utils.WorkerHandler;
import com.otaliastudios.cameraview.preview.CameraPreview; import com.otaliastudios.cameraview.preview.CameraPreview;
import com.otaliastudios.cameraview.preview.GlCameraPreview; import com.otaliastudios.cameraview.preview.GlCameraPreview;
import com.otaliastudios.cameraview.preview.RendererFrameCallback;
import com.otaliastudios.cameraview.preview.RendererThread; import com.otaliastudios.cameraview.preview.RendererThread;
import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
@ -70,7 +71,7 @@ public class SnapshotPictureRecorder extends PictureRecorder {
@TargetApi(Build.VERSION_CODES.KITKAT) @TargetApi(Build.VERSION_CODES.KITKAT)
private void takeGl(@NonNull final GlCameraPreview preview) { private void takeGl(@NonNull final GlCameraPreview preview) {
preview.addRendererFrameCallback(new GlCameraPreview.RendererFrameCallback() { preview.addRendererFrameCallback(new RendererFrameCallback() {
int mTextureId; int mTextureId;
SurfaceTexture mSurfaceTexture; SurfaceTexture mSurfaceTexture;
@ -89,7 +90,7 @@ public class SnapshotPictureRecorder extends PictureRecorder {
@RendererThread @RendererThread
@Override @Override
public void onRendererFrame(SurfaceTexture surfaceTexture, final float scaleX, final float scaleY) { public void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, final float scaleX, final float scaleY) {
preview.removeRendererFrameCallback(this); preview.removeRendererFrameCallback(this);
// This kinda work but has drawbacks: // This kinda work but has drawbacks:

@ -3,12 +3,14 @@ package com.otaliastudios.cameraview.preview;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.Task;
import com.otaliastudios.cameraview.engine.CameraEngine; import com.otaliastudios.cameraview.engine.CameraEngine;
import com.otaliastudios.cameraview.internal.utils.Task;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
/** /**
@ -22,99 +24,176 @@ public abstract class CameraPreview<T extends View, Output> {
protected final static CameraLogger LOG = CameraLogger.create(CameraPreview.class.getSimpleName()); protected final static CameraLogger LOG = CameraLogger.create(CameraPreview.class.getSimpleName());
// Used for testing. /**
Task<Void> mCropTask = new Task<>(); * This is used to notify CameraEngine to recompute its camera Preview size.
* After that, CameraView will need a new layout pass to adapt to the Preview size.
// This is used to notify CameraEngine to recompute its camera Preview size. */
// After that, CameraView will need a new layout pass to adapt to the Preview size.
public interface SurfaceCallback { public interface SurfaceCallback {
/**
* Called when the surface is available.
*/
void onSurfaceAvailable(); void onSurfaceAvailable();
/**
* Called when the surface has changed.
*/
void onSurfaceChanged(); void onSurfaceChanged();
/**
* Called when the surface was destroyed.
*/
void onSurfaceDestroyed(); void onSurfaceDestroyed();
} }
@VisibleForTesting Task<Void> mCropTask = new Task<>();
private SurfaceCallback mSurfaceCallback; private SurfaceCallback mSurfaceCallback;
private T mView; private T mView;
protected boolean mCropping; boolean mCropping;
// These are the surface dimensions in REF_VIEW. // These are the surface dimensions in REF_VIEW.
protected int mOutputSurfaceWidth; int mOutputSurfaceWidth;
protected int mOutputSurfaceHeight; int mOutputSurfaceHeight;
// These are the preview stream dimensions, in REF_VIEW. // These are the preview stream dimensions, in REF_VIEW.
protected int mInputStreamWidth; int mInputStreamWidth;
protected int mInputStreamHeight; int mInputStreamHeight;
protected boolean mInputFlipped; private boolean mInputFlipped;
CameraPreview(@NonNull Context context, @NonNull ViewGroup parent, @Nullable SurfaceCallback callback) { /**
* Creates a new preview.
* @param context a context
* @param parent where to inflate our view
* @param callback the callback
*/
public CameraPreview(@NonNull Context context, @NonNull ViewGroup parent, @Nullable SurfaceCallback callback) {
mView = onCreateView(context, parent); mView = onCreateView(context, parent);
mSurfaceCallback = callback; mSurfaceCallback = callback;
} }
/**
* Sets a callback to be notified of surface events (creation, change, destruction)
* @param callback a callback
*/
public final void setSurfaceCallback(@NonNull SurfaceCallback callback) {
mSurfaceCallback = callback;
// If surface already available, dispatch.
if (mOutputSurfaceWidth != 0 || mOutputSurfaceHeight != 0) {
mSurfaceCallback.onSurfaceAvailable();
}
}
/**
* Called at creation time. Implementors should inflate the hierarchy into the
* parent ViewGroup, and return the View that actually hosts the surface.
*
* @param context a context
* @param parent where to inflate
* @return the view hosting the Surface
*/
@NonNull @NonNull
protected abstract T onCreateView(@NonNull Context context, @NonNull ViewGroup parent); protected abstract T onCreateView(@NonNull Context context, @NonNull ViewGroup parent);
/**
* Returns the view hosting the Surface.
* @return the view
*/
@NonNull @NonNull
public final T getView() { public final T getView() {
return mView; return mView;
} }
// Only used in tests /**
* For testing purposes, should return the root view that was inflated into the
* parent during {@link #onCreateView(Context, ViewGroup)}.
* @return the root view
*/
@SuppressWarnings("unused")
@VisibleForTesting
@NonNull @NonNull
abstract View getRootView(); abstract View getRootView();
/**
* Returns the output surface object (for example a SurfaceHolder
* or a SurfaceTexture).
* @return the surface object
*/
@NonNull @NonNull
public abstract Class<Output> getOutputClass(); public abstract Output getOutput();
/**
* Returns the type of the output returned by {@link #getOutput()}.
* @return the output type
*/
@NonNull @NonNull
public abstract Output getOutput(); public abstract Class<Output> getOutputClass();
// As far as I can see, these are the actual preview dimensions, as set in CameraParameters. /**
// This is called by the CameraImpl. * Called to notify the preview of the input stream size. The width and height must be
// These must be alredy rotated, if needed, to be consistent with surface/view sizes. * rotated before calling this, if needed, to be consistent with the VIEW reference.
*
* @param width width of the preview stream, in view coordinates
* @param height height of the preview stream, in view coordinates
* @param wasFlipped whether width and height were flipped before calling this
*/
public void setStreamSize(int width, int height, boolean wasFlipped) { public void setStreamSize(int width, int height, boolean wasFlipped) {
LOG.i("setStreamSize:", "desiredW=", width, "desiredH=", height); LOG.i("setStreamSize:", "desiredW=", width, "desiredH=", height);
mInputStreamWidth = width; mInputStreamWidth = width;
mInputStreamHeight = height; mInputStreamHeight = height;
mInputFlipped = wasFlipped; mInputFlipped = wasFlipped;
if (mInputStreamWidth > 0 && mInputStreamHeight > 0) { if (mInputStreamWidth > 0 && mInputStreamHeight > 0) {
crop(); crop(mCropTask);
} }
} }
/**
* Returns the current input stream size, in view coordinates.
* @return the current input stream size
*/
@SuppressWarnings("unused")
@NonNull @NonNull
final Size getStreamSize() { final Size getStreamSize() {
return new Size(mInputStreamWidth, mInputStreamHeight); return new Size(mInputStreamWidth, mInputStreamHeight);
} }
/**
* Returns the current output surface size, in view coordinates.
* @return the current output surface size.
*/
@NonNull @NonNull
public final Size getSurfaceSize() { public final Size getSurfaceSize() {
return new Size(mOutputSurfaceWidth, mOutputSurfaceHeight); return new Size(mOutputSurfaceWidth, mOutputSurfaceHeight);
} }
public final void setSurfaceCallback(@NonNull SurfaceCallback callback) { /**
mSurfaceCallback = callback; * Whether we have a valid surface already.
// If surface already available, dispatch. * @return whether we have a surface
if (mOutputSurfaceWidth != 0 || mOutputSurfaceHeight != 0) { */
mSurfaceCallback.onSurfaceAvailable(); public final boolean hasSurface() {
} return mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0;
} }
/**
* Subclasses can call this to notify that the surface is available.
* @param width surface width
* @param height surface height
*/
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
protected final void dispatchOnSurfaceAvailable(int width, int height) { protected final void dispatchOnSurfaceAvailable(int width, int height) {
LOG.i("dispatchOnSurfaceAvailable:", "w=", width, "h=", height); LOG.i("dispatchOnSurfaceAvailable:", "w=", width, "h=", height);
mOutputSurfaceWidth = width; mOutputSurfaceWidth = width;
mOutputSurfaceHeight = height; mOutputSurfaceHeight = height;
if (mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0) { if (mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0) {
crop(); crop(mCropTask);
} }
mSurfaceCallback.onSurfaceAvailable(); mSurfaceCallback.onSurfaceAvailable();
} }
/**
// As far as I can see, these are the view/surface dimensions. * Subclasses can call this to notify that the surface has changed.
// This is called by subclasses. * @param width surface width
* @param height surface height
*/
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
protected final void dispatchOnSurfaceSizeChanged(int width, int height) { protected final void dispatchOnSurfaceSizeChanged(int width, int height) {
LOG.i("dispatchOnSurfaceSizeChanged:", "w=", width, "h=", height); LOG.i("dispatchOnSurfaceSizeChanged:", "w=", width, "h=", height);
@ -122,12 +201,15 @@ public abstract class CameraPreview<T extends View, Output> {
mOutputSurfaceWidth = width; mOutputSurfaceWidth = width;
mOutputSurfaceHeight = height; mOutputSurfaceHeight = height;
if (width > 0 && height > 0) { if (width > 0 && height > 0) {
crop(); crop(mCropTask);
} }
mSurfaceCallback.onSurfaceChanged(); mSurfaceCallback.onSurfaceChanged();
} }
} }
/**
* Subclasses can call this to notify that the surface has been destroyed.
*/
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
protected final void dispatchOnSurfaceDestroyed() { protected final void dispatchOnSurfaceDestroyed() {
mOutputSurfaceWidth = 0; mOutputSurfaceWidth = 0;
@ -135,19 +217,24 @@ public abstract class CameraPreview<T extends View, Output> {
mSurfaceCallback.onSurfaceDestroyed(); mSurfaceCallback.onSurfaceDestroyed();
} }
// Public for mockito (CameraViewTest) /**
* Called by the hosting {@link com.otaliastudios.cameraview.CameraView},
* this is a lifecycle event.
*/
public void onResume() {} public void onResume() {}
// Public for mockito (CameraViewTest) /**
* Called by the hosting {@link com.otaliastudios.cameraview.CameraView},
* this is a lifecycle event.
*/
public void onPause() {} public void onPause() {}
// Public for mockito (CameraViewTest) /**
* Called by the hosting {@link com.otaliastudios.cameraview.CameraView},
* this is a lifecycle event.
*/
public void onDestroy() {} public void onDestroy() {}
public final boolean hasSurface() {
return mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0;
}
/** /**
* Here we must crop the visible part by applying a > 1 scale to one of our * Here we must crop the visible part by applying a > 1 scale to one of our
* dimensions. This way our internal aspect ratio (mOutputSurfaceWidth / mOutputSurfaceHeight) * dimensions. This way our internal aspect ratio (mOutputSurfaceWidth / mOutputSurfaceHeight)
@ -156,22 +243,27 @@ public abstract class CameraPreview<T extends View, Output> {
* There might still be some absolute difference (e.g. same ratio but bigger / smaller). * There might still be some absolute difference (e.g. same ratio but bigger / smaller).
* However that should be already managed by the framework. * However that should be already managed by the framework.
*/ */
protected void crop() { protected void crop(@NonNull Task<Void> task) {
// The base implementation does not support cropping. // The base implementation does not support cropping.
mCropTask.start(); task.start();
mCropTask.end(null); task.end(null);
} }
/**
* Whether this preview implementation supports cropping.
* The base implementation does not, but it is strongly recommended to do so.
* @return true if cropping is supported
*/
public boolean supportsCropping() { public boolean supportsCropping() {
return false; return false;
} }
/** /**
* Whether we are cropping the output. * Whether we are currently cropping the output.
* If false, this means that the output image will match the visible bounds. * If false, this means that the output image will match the visible bounds.
* @return true if cropping * @return true if cropping
*/ */
/* not final for tests */ boolean isCropping() { public boolean isCropping() {
return mCropping; return mCropping;
} }
} }

@ -6,16 +6,16 @@ import android.opengl.GLSurfaceView;
import android.opengl.Matrix; import android.opengl.Matrix;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.otaliastudios.cameraview.EglViewport;
import com.otaliastudios.cameraview.R; import com.otaliastudios.cameraview.R;
import com.otaliastudios.cameraview.RendererThread; import com.otaliastudios.cameraview.internal.egl.EglViewport;
import com.otaliastudios.cameraview.preview.CameraPreview; import com.otaliastudios.cameraview.internal.utils.Task;
import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.AspectRatio;
import java.util.Collections; import java.util.Collections;
@ -65,11 +65,34 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
private SurfaceTexture mInputSurfaceTexture; private SurfaceTexture mInputSurfaceTexture;
private EglViewport mOutputViewport; private EglViewport mOutputViewport;
private Set<RendererFrameCallback> mRendererFrameCallbacks = Collections.synchronizedSet(new HashSet<RendererFrameCallback>()); private Set<RendererFrameCallback> mRendererFrameCallbacks = Collections.synchronizedSet(new HashSet<RendererFrameCallback>());
/* for tests */ float mScaleX = 1F; @VisibleForTesting float mScaleX = 1F;
/* for tests */ float mScaleY = 1F; @VisibleForTesting float mScaleY = 1F;
private View mRootView; private View mRootView;
/**
* Method specific to the GL preview. Adds a {@link RendererFrameCallback}
* to receive renderer frame events.
* @param callback a callback
*/
public void addRendererFrameCallback(@NonNull final RendererFrameCallback callback) {
getView().queueEvent(new Runnable() {
@Override
public void run() {
mRendererFrameCallbacks.add(callback);
if (mOutputTextureId != 0) callback.onRendererTextureCreated(mOutputTextureId);
}
});
}
/**
* Method specific to the GL preview. Removes a {@link RendererFrameCallback}
* that was previously added to receive renderer frame events.
* @param callback a callback
*/
public void removeRendererFrameCallback(@NonNull final RendererFrameCallback callback) {
mRendererFrameCallbacks.remove(callback);
}
public GlCameraPreview(@NonNull Context context, @NonNull ViewGroup parent, @Nullable SurfaceCallback callback) { public GlCameraPreview(@NonNull Context context, @NonNull ViewGroup parent, @Nullable SurfaceCallback callback) {
super(context, parent, callback); super(context, parent, callback);
} }
@ -205,18 +228,18 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
@NonNull @NonNull
@Override @Override
Class<SurfaceTexture> getOutputClass() { public Class<SurfaceTexture> getOutputClass() {
return SurfaceTexture.class; return SurfaceTexture.class;
} }
@NonNull @NonNull
@Override @Override
SurfaceTexture getOutput() { public SurfaceTexture getOutput() {
return mInputSurfaceTexture; return mInputSurfaceTexture;
} }
@Override @Override
boolean supportsCropping() { public boolean supportsCropping() {
return true; return true;
} }
@ -233,8 +256,8 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
* then drawing it with a scaled transformation matrix. See {@link #onDrawFrame(GL10)}. * then drawing it with a scaled transformation matrix. See {@link #onDrawFrame(GL10)}.
*/ */
@Override @Override
protected void crop() { protected void crop(@NonNull Task<Void> task) {
mCropTask.start(); task.start();
if (mInputStreamWidth > 0 && mInputStreamHeight > 0 && mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0) { if (mInputStreamWidth > 0 && mInputStreamHeight > 0 && mOutputSurfaceWidth > 0 && mOutputSurfaceHeight > 0) {
float scaleX = 1f, scaleY = 1f; float scaleX = 1f, scaleY = 1f;
AspectRatio current = AspectRatio.of(mOutputSurfaceWidth, mOutputSurfaceHeight); AspectRatio current = AspectRatio.of(mOutputSurfaceWidth, mOutputSurfaceHeight);
@ -251,44 +274,6 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
mScaleY = 1F / scaleY; mScaleY = 1F / scaleY;
getView().requestRender(); getView().requestRender();
} }
mCropTask.end(null); task.end(null);
}
interface RendererFrameCallback {
/**
* Called on the renderer thread, hopefully only once, to notify that
* the texture was created (or to inform a new callback of the old texture).
*
* @param textureId the GL texture linked to the image stream
*/
@RendererThread
void onRendererTextureCreated(int textureId);
/**
* Called on the renderer thread after each frame was drawn.
* You are not supposed to hold for too long onto this thread, because
* well, it is the rendering thread.
*
* @param surfaceTexture the texture to get transformation
* @param scaleX the scaleX (in REF_VIEW) value
* @param scaleY the scaleY (in REF_VIEW) value
*/
@RendererThread
void onRendererFrame(SurfaceTexture surfaceTexture, float scaleX, float scaleY);
}
void addRendererFrameCallback(@NonNull final RendererFrameCallback callback) {
getView().queueEvent(new Runnable() {
@Override
public void run() {
mRendererFrameCallbacks.add(callback);
if (mOutputTextureId != 0) callback.onRendererTextureCreated(mOutputTextureId);
}
});
}
void removeRendererFrameCallback(@NonNull final RendererFrameCallback callback) {
mRendererFrameCallbacks.remove(callback);
} }
} }

@ -0,0 +1,32 @@
package com.otaliastudios.cameraview.preview;
import android.graphics.SurfaceTexture;
import androidx.annotation.NonNull;
/**
* Callback for renderer frames.
*/
public interface RendererFrameCallback {
/**
* Called on the renderer thread, hopefully only once, to notify that
* the texture was created (or to inform a new callback of the old texture).
*
* @param textureId the GL texture linked to the image stream
*/
@RendererThread
void onRendererTextureCreated(int textureId);
/**
* Called on the renderer thread after each frame was drawn.
* You are not supposed to hold for too long onto this thread, because
* well, it is the rendering thread.
*
* @param surfaceTexture the texture to get transformation
* @param scaleX the scaleX (in REF_VIEW) value
* @param scaleY the scaleY (in REF_VIEW) value
*/
@RendererThread
void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, float scaleX, float scaleY);
}

@ -1,3 +1,6 @@
package com.otaliastudios.cameraview.preview; package com.otaliastudios.cameraview.preview;
/**
* Indicates that some action is being executed on the renderer thread.
*/
public @interface RendererThread {} public @interface RendererThread {}

@ -12,10 +12,13 @@ import android.view.ViewGroup;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.R; import com.otaliastudios.cameraview.R;
// Fallback preview when hardware acceleration is off. /**
// Currently this does NOT support cropping (e. g. the crop inside behavior), * This is the fallback preview when hardware acceleration is off, and is the last resort.
// so we return false in supportsCropping() in order to have proper measuring. * Currently does not support cropping, which means that {@link com.otaliastudios.cameraview.CameraView}
// This means that CameraView is forced to be wrap_content. * is forced to be wrap_content.
*
* Do not use.
*/
public class SurfaceCameraPreview extends CameraPreview<SurfaceView, SurfaceHolder> { public 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());
@ -34,6 +37,7 @@ public class SurfaceCameraPreview extends CameraPreview<SurfaceView, SurfaceHold
parent.addView(root, 0); parent.addView(root, 0);
SurfaceView surfaceView = root.findViewById(R.id.surface_view); SurfaceView surfaceView = root.findViewById(R.id.surface_view);
final SurfaceHolder holder = surfaceView.getHolder(); final SurfaceHolder holder = surfaceView.getHolder();
//noinspection deprecation
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(new SurfaceHolder.Callback() { holder.addCallback(new SurfaceHolder.Callback() {
@ -73,13 +77,13 @@ public class SurfaceCameraPreview extends CameraPreview<SurfaceView, SurfaceHold
@NonNull @NonNull
@Override @Override
SurfaceHolder getOutput() { public SurfaceHolder getOutput() {
return getView().getHolder(); return getView().getHolder();
} }
@NonNull @NonNull
@Override @Override
Class<SurfaceHolder> getOutputClass() { public Class<SurfaceHolder> getOutputClass() {
return SurfaceHolder.class; return SurfaceHolder.class;
} }

@ -11,9 +11,13 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.otaliastudios.cameraview.R; import com.otaliastudios.cameraview.R;
import com.otaliastudios.cameraview.preview.CameraPreview; import com.otaliastudios.cameraview.internal.utils.Task;
import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.AspectRatio;
/**
* A preview implementation based on {@link TextureView}.
* Better than {@link SurfaceCameraPreview} but much less powerful than {@link GlCameraPreview}.
*/
public class TextureCameraPreview extends CameraPreview<TextureView, SurfaceTexture> { public class TextureCameraPreview extends CameraPreview<TextureView, SurfaceTexture> {
private View mRootView; private View mRootView;
@ -62,19 +66,19 @@ public class TextureCameraPreview extends CameraPreview<TextureView, SurfaceText
@NonNull @NonNull
@Override @Override
Class<SurfaceTexture> getOutputClass() { public Class<SurfaceTexture> getOutputClass() {
return SurfaceTexture.class; return SurfaceTexture.class;
} }
@NonNull @NonNull
@Override @Override
SurfaceTexture getOutput() { public SurfaceTexture getOutput() {
return getView().getSurfaceTexture(); return getView().getSurfaceTexture();
} }
@TargetApi(15) @TargetApi(15)
@Override @Override
void setStreamSize(int width, int height, boolean wasFlipped) { public void setStreamSize(int width, int height, boolean wasFlipped) {
super.setStreamSize(width, height, wasFlipped); super.setStreamSize(width, height, wasFlipped);
if (getView().getSurfaceTexture() != null) { if (getView().getSurfaceTexture() != null) {
getView().getSurfaceTexture().setDefaultBufferSize(width, height); getView().getSurfaceTexture().setDefaultBufferSize(width, height);
@ -82,19 +86,19 @@ public class TextureCameraPreview extends CameraPreview<TextureView, SurfaceText
} }
@Override @Override
boolean supportsCropping() { public boolean supportsCropping() {
return true; return true;
} }
@Override @Override
protected void crop() { protected void crop(final @NonNull Task<Void> task) {
mCropTask.start(); task.start();
getView().post(new Runnable() { getView().post(new Runnable() {
@Override @Override
public void run() { public void run() {
if (mInputStreamHeight == 0 || mInputStreamWidth == 0 || if (mInputStreamHeight == 0 || mInputStreamWidth == 0 ||
mOutputSurfaceHeight == 0 || mOutputSurfaceWidth == 0) { mOutputSurfaceHeight == 0 || mOutputSurfaceWidth == 0) {
mCropTask.end(null); task.end(null);
return; return;
} }
float scaleX = 1f, scaleY = 1f; float scaleX = 1f, scaleY = 1f;
@ -114,7 +118,7 @@ public class TextureCameraPreview extends CameraPreview<TextureView, SurfaceText
mCropping = scaleX > 1.02f || scaleY > 1.02f; mCropping = scaleX > 1.02f || scaleY > 1.02f;
LOG.i("crop:", "applied scaleX=", scaleX); LOG.i("crop:", "applied scaleX=", scaleX);
LOG.i("crop:", "applied scaleY=", scaleY); LOG.i("crop:", "applied scaleY=", scaleY);
mCropTask.end(null); task.end(null);
} }
}); });
} }

@ -12,6 +12,7 @@ import com.otaliastudios.cameraview.VideoResult;
import com.otaliastudios.cameraview.controls.Audio; import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.controls.VideoCodec; import com.otaliastudios.cameraview.controls.VideoCodec;
import com.otaliastudios.cameraview.preview.GlCameraPreview; import com.otaliastudios.cameraview.preview.GlCameraPreview;
import com.otaliastudios.cameraview.preview.RendererFrameCallback;
import com.otaliastudios.cameraview.preview.RendererThread; import com.otaliastudios.cameraview.preview.RendererThread;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
@ -23,7 +24,7 @@ import androidx.annotation.RequiresApi;
* A {@link VideoRecorder} that uses {@link android.media.MediaCodec} APIs. * A {@link VideoRecorder} that uses {@link android.media.MediaCodec} APIs.
*/ */
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class SnapshotVideoRecorder extends VideoRecorder implements GlCameraPreview.RendererFrameCallback, public class SnapshotVideoRecorder extends VideoRecorder implements RendererFrameCallback,
MediaEncoderEngine.Listener { MediaEncoderEngine.Listener {
private static final String TAG = SnapshotVideoRecorder.class.getSimpleName(); private static final String TAG = SnapshotVideoRecorder.class.getSimpleName();

Loading…
Cancel
Save