Create FilterCameraPreview, improve GlCameraPreview

pull/535/head
Mattia Iavarone 6 years ago
parent 084a9b239b
commit 35e7bdacd8
  1. 27
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  2. 10
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/Filters.java
  3. 8
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java
  4. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java
  5. 26
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/FilterCameraPreview.java
  6. 42
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java
  7. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/RendererFrameCallback.java
  8. 25
      cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java

@ -68,6 +68,7 @@ import com.otaliastudios.cameraview.markers.MarkerLayout;
import com.otaliastudios.cameraview.markers.MarkerParser; import com.otaliastudios.cameraview.markers.MarkerParser;
import com.otaliastudios.cameraview.overlay.OverlayLayout; import com.otaliastudios.cameraview.overlay.OverlayLayout;
import com.otaliastudios.cameraview.preview.CameraPreview; import com.otaliastudios.cameraview.preview.CameraPreview;
import com.otaliastudios.cameraview.preview.FilterCameraPreview;
import com.otaliastudios.cameraview.preview.GlCameraPreview; import com.otaliastudios.cameraview.preview.GlCameraPreview;
import com.otaliastudios.cameraview.preview.SurfaceCameraPreview; import com.otaliastudios.cameraview.preview.SurfaceCameraPreview;
import com.otaliastudios.cameraview.preview.TextureCameraPreview; import com.otaliastudios.cameraview.preview.TextureCameraPreview;
@ -2132,17 +2133,27 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
//endregion //endregion
//region Effects //region Filters
public void setFilter(@NonNull Filters filter) {
setFilter(filter.newInstance());
}
/**
* Applies a real-time filter to the camera preview, if it supports it.
* The only preview type that does so is currently {@link Preview#GL_SURFACE}.
*
* The filter will be applied to any picture snapshot taken with
* {@link #takePictureSnapshot()} and any video snapshot taken with
* {@link #takeVideoSnapshot(File)}.
*
* Use {@link com.otaliastudios.cameraview.filters.NoFilter} to clear the existing filter,
* and take a look at the {@link Filters} class for commonly used filters.
*
* @see Filters
* @param filter a new filter
*/
public void setFilter(@NonNull Filter filter) { public void setFilter(@NonNull Filter filter) {
if (mCameraPreview instanceof GlCameraPreview) { if (mCameraPreview instanceof FilterCameraPreview) {
((GlCameraPreview) mCameraPreview).setShaderEffect(filter); ((FilterCameraPreview) mCameraPreview).setFilter(filter);
} else { } else {
LOG.w("setFilter", "setFilter is supported only for GLSurfaceView"); LOG.w("setFilter() is supported only for GL_SURFACE. Preview:", mPreview);
} }
} }

@ -2,6 +2,12 @@ package com.otaliastudios.cameraview.filters;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
/**
* Contains commonly used {@link Filter}s.
*
* You can use {@link #newInstance()} to create a new instance and
* pass it to {@link com.otaliastudios.cameraview.CameraView#setFilter(Filter)}.
*/
public enum Filters { public enum Filters {
NONE(NoFilter.class), NONE(NoFilter.class),
AUTO_FIX(AutoFixFilter.class), AUTO_FIX(AutoFixFilter.class),
@ -32,6 +38,10 @@ public enum Filters {
this.filterClass = filterClass; this.filterClass = filterClass;
} }
/**
* Returns a new instance of the given filter.
* @return a new instance
*/
@NonNull @NonNull
public Filter newInstance() { public Filter newInstance() {
try { try {

@ -63,11 +63,13 @@ public class EglViewport extends EglElement {
private boolean mIsShaderChanged = false; private boolean mIsShaderChanged = false;
public EglViewport() { public EglViewport() {
this(new NoFilter());
}
public EglViewport(@NonNull Filter filter) {
mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
mTextureUnit = GLES20.GL_TEXTURE0; mTextureUnit = GLES20.GL_TEXTURE0;
mShaderEffect = filter;
//init the default shader effect
mShaderEffect = new NoFilter();
initProgram(); initProgram();
} }

@ -97,9 +97,9 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
@RendererThread @RendererThread
@Override @Override
public void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, final float scaleX, final float scaleY, Filter shaderEffect) { public void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, final float scaleX, final float scaleY) {
mPreview.removeRendererFrameCallback(this); mPreview.removeRendererFrameCallback(this);
SnapshotGlPictureRecorder.this.onRendererFrame(surfaceTexture, scaleX, scaleY, shaderEffect); SnapshotGlPictureRecorder.this.onRendererFrame(surfaceTexture, scaleX, scaleY);
} }
@Override @Override
@ -151,7 +151,7 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
*/ */
@RendererThread @RendererThread
@TargetApi(Build.VERSION_CODES.KITKAT) @TargetApi(Build.VERSION_CODES.KITKAT)
private void onRendererFrame(final @NonNull SurfaceTexture surfaceTexture, final float scaleX, final float scaleY, @NonNull Filter filter) { private void onRendererFrame(final @NonNull SurfaceTexture surfaceTexture, final float scaleX, final float scaleY) {
// Get egl context from the RendererThread, which is the one in which we have created // Get egl context from the RendererThread, which is the one in which we have created
// the textureId and the overlayTextureId, managed by the GlSurfaceView. // the textureId and the overlayTextureId, managed by the GlSurfaceView.
// Next operations can then be performed on different threads using this handle. // Next operations can then be performed on different threads using this handle.

@ -0,0 +1,26 @@
package com.otaliastudios.cameraview.preview;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filters.Filter;
/**
* A preview that support GL filters defined through the {@link Filter} interface.
*/
public abstract class FilterCameraPreview<T extends View, Output> extends CameraPreview<T, Output> {
public FilterCameraPreview(@NonNull Context context, @NonNull ViewGroup parent) {
super(context, parent);
}
public abstract void setFilter(@NonNull Filter filter);
@NonNull
public abstract Filter getCurrentFilter();
}

@ -58,7 +58,7 @@ import javax.microedition.khronos.opengles.GL10;
* Callbacks are guaranteed to be called on the renderer thread, which means that we can fetch * Callbacks are guaranteed to be called on the renderer thread, which means that we can fetch
* the GL context that was created and is managed by the {@link GLSurfaceView}. * the GL context that was created and is managed by the {@link GLSurfaceView}.
*/ */
public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> { public class GlCameraPreview extends FilterCameraPreview<GLSurfaceView, SurfaceTexture> {
private boolean mDispatched; private boolean mDispatched;
private final float[] mTransformMatrix = new float[16]; private final float[] mTransformMatrix = new float[16];
@ -69,8 +69,7 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
@VisibleForTesting float mCropScaleX = 1F; @VisibleForTesting float mCropScaleX = 1F;
@VisibleForTesting float mCropScaleY = 1F; @VisibleForTesting float mCropScaleY = 1F;
private View mRootView; private View mRootView;
private Filter mCurrentFilter;
private Filter mCurrentShaderEffect;
public GlCameraPreview(@NonNull Context context, @NonNull ViewGroup parent) { public GlCameraPreview(@NonNull Context context, @NonNull ViewGroup parent) {
super(context, parent); super(context, parent);
@ -142,7 +141,8 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
@RendererThread @RendererThread
@Override @Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) { public void onSurfaceCreated(GL10 gl, EGLConfig config) {
mOutputViewport = new EglViewport(); if (mCurrentFilter == null) mCurrentFilter = new NoFilter();
mOutputViewport = new EglViewport(mCurrentFilter);
mOutputTextureId = mOutputViewport.createTexture(); mOutputTextureId = mOutputViewport.createTexture();
mInputSurfaceTexture = new SurfaceTexture(mOutputTextureId); mInputSurfaceTexture = new SurfaceTexture(mOutputTextureId);
getView().queueEvent(new Runnable() { getView().queueEvent(new Runnable() {
@ -165,15 +165,13 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
getView().requestRender(); // requestRender is thread-safe. getView().requestRender(); // requestRender is thread-safe.
} }
}); });
//init the default shader effect
mCurrentShaderEffect = new NoFilter();
} }
@RendererThread @RendererThread
@Override @Override
public void onSurfaceChanged(GL10 gl, final int width, final int height) { public void onSurfaceChanged(GL10 gl, final int width, final int height) {
gl.glViewport(0, 0, width, height); gl.glViewport(0, 0, width, height);
mCurrentFilter.setPreviewingViewSize(width, height);
if (!mDispatched) { if (!mDispatched) {
dispatchOnSurfaceAvailable(width, height); dispatchOnSurfaceAvailable(width, height);
mDispatched = true; mDispatched = true;
@ -216,7 +214,7 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
synchronized (mRendererFrameCallbacks) { synchronized (mRendererFrameCallbacks) {
// Need to synchronize when iterating the Collections.synchronizedSet // Need to synchronize when iterating the Collections.synchronizedSet
for (RendererFrameCallback callback : mRendererFrameCallbacks) { for (RendererFrameCallback callback : mRendererFrameCallbacks) {
callback.onRendererFrame(mInputSurfaceTexture, mCropScaleX, mCropScaleY, mCurrentShaderEffect); callback.onRendererFrame(mInputSurfaceTexture, mCropScaleX, mCropScaleY);
} }
} }
} }
@ -284,7 +282,7 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
public void run() { public void run() {
mRendererFrameCallbacks.add(callback); mRendererFrameCallbacks.add(callback);
if (mOutputTextureId != 0) callback.onRendererTextureCreated(mOutputTextureId); if (mOutputTextureId != 0) callback.onRendererTextureCreated(mOutputTextureId);
callback.onFilterChanged(mCurrentShaderEffect); callback.onFilterChanged(mCurrentFilter);
} }
}); });
} }
@ -317,14 +315,30 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
return new Renderer(); return new Renderer();
} }
public void setShaderEffect(@NonNull Filter shaderEffect){ //region Filters
shaderEffect.setPreviewingViewSize(getView().getWidth(), getView().getHeight());
mCurrentShaderEffect = shaderEffect;
@NonNull
@Override
public Filter getCurrentFilter() {
return mCurrentFilter;
}
mOutputViewport.changeShaderFilter(shaderEffect); @Override
public void setFilter(@NonNull Filter filter) {
if (hasSurface()) {
filter.setPreviewingViewSize(mOutputSurfaceWidth, mOutputSurfaceHeight);
}
mCurrentFilter = filter;
if (mOutputViewport != null) {
mOutputViewport.changeShaderFilter(filter);
}
// Need to synchronize when iterating the Collections.synchronizedSet
synchronized (mRendererFrameCallbacks) {
for (RendererFrameCallback callback : mRendererFrameCallbacks) { for (RendererFrameCallback callback : mRendererFrameCallbacks) {
callback.onFilterChanged(shaderEffect); callback.onFilterChanged(filter);
}
} }
} }
} }

@ -30,7 +30,7 @@ public interface RendererFrameCallback {
* @param scaleY the scaleY (in REF_VIEW) value * @param scaleY the scaleY (in REF_VIEW) value
*/ */
@RendererThread @RendererThread
void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, float scaleX, float scaleY, Filter shaderEffect); void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, float scaleX, float scaleY);
/** /**
* Called on the change of shader filter * Called on the change of shader filter

@ -102,9 +102,17 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
} }
} }
@Override
public void onFilterChanged(@NonNull Filter filter) {
mCurrentFilter = filter;
if (mEncoderEngine != null) {
mEncoderEngine.notify(TextureMediaEncoder.FILTER_EVENT, filter);
}
}
@RendererThread @RendererThread
@Override @Override
public void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, float scaleX, float scaleY, Filter shaderEffect) { public void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, float scaleX, float scaleY) {
if (mCurrentState == STATE_NOT_RECORDING && mDesiredState == STATE_RECORDING) { if (mCurrentState == STATE_NOT_RECORDING && mDesiredState == STATE_RECORDING) {
LOG.i("Starting the encoder engine."); LOG.i("Starting the encoder engine.");
@ -160,13 +168,7 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
// Engine // Engine
mEncoderEngine = new MediaEncoderEngine(mResult.file, videoEncoder, audioEncoder, mEncoderEngine = new MediaEncoderEngine(mResult.file, videoEncoder, audioEncoder,
mResult.maxDuration, mResult.maxSize, SnapshotVideoRecorder.this); mResult.maxDuration, mResult.maxSize, SnapshotVideoRecorder.this);
//set current filter
if (mEncoderEngine != null) {
mEncoderEngine.notify(TextureMediaEncoder.FILTER_EVENT, mCurrentFilter); mEncoderEngine.notify(TextureMediaEncoder.FILTER_EVENT, mCurrentFilter);
}
mEncoderEngine.start(); mEncoderEngine.start();
mResult.rotation = 0; // We will rotate the result instead. mResult.rotation = 0; // We will rotate the result instead.
mCurrentState = STATE_RECORDING; mCurrentState = STATE_RECORDING;
@ -192,15 +194,6 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
} }
@Override
public void onFilterChanged(@NonNull Filter filter) {
mCurrentFilter = filter;
if (mEncoderEngine != null) {
mEncoderEngine.notify(TextureMediaEncoder.FILTER_EVENT, filter);
}
}
@Override @Override
public void onEncodingStart() { public void onEncodingStart() {
dispatchVideoRecordingStart(); dispatchVideoRecordingStart();

Loading…
Cancel
Save