parent
f6e703035b
commit
f3dd152a72
@ -0,0 +1,2 @@ |
||||
/build |
||||
/.cxx |
@ -0,0 +1,26 @@ |
||||
plugins { |
||||
id 'com.android.library' |
||||
} |
||||
|
||||
android { |
||||
compileSdkVersion rootProject.ext.compileSdkVersion |
||||
|
||||
defaultConfig { |
||||
minSdkVersion rootProject.ext.minSdkVersion |
||||
targetSdkVersion rootProject.ext.targetSdkVersion |
||||
} |
||||
|
||||
buildTypes { |
||||
release { |
||||
minifyEnabled false |
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
dependencies { |
||||
|
||||
implementation "androidx.appcompat:appcompat:$rootProject.appcompatVersion" |
||||
|
||||
} |
@ -0,0 +1,21 @@ |
||||
# Add project specific ProGuard rules here. |
||||
# You can control the set of applied configuration files using the |
||||
# proguardFiles setting in build.gradle. |
||||
# |
||||
# For more details, see |
||||
# http://developer.android.com/guide/developing/tools/proguard.html |
||||
|
||||
# If your project uses WebView with JS, uncomment the following |
||||
# and specify the fully qualified class name to the JavaScript interface |
||||
# class: |
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { |
||||
# public *; |
||||
#} |
||||
|
||||
# Uncomment this to preserve the line number information for |
||||
# debugging stack traces. |
||||
#-keepattributes SourceFile,LineNumberTable |
||||
|
||||
# If you keep the line number information, uncomment this to |
||||
# hide the original source file name. |
||||
#-renamesourcefileattribute SourceFile |
@ -0,0 +1,7 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||
package="com.frank.camerafilter"> |
||||
|
||||
<uses-feature android:glEsVersion="0x00030000" android:required="true"/> |
||||
|
||||
</manifest> |
@ -0,0 +1,136 @@ |
||||
package com.frank.camerafilter.camera; |
||||
|
||||
import android.graphics.SurfaceTexture; |
||||
import android.hardware.Camera; |
||||
import android.hardware.Camera.Parameters; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* @author xufulong |
||||
* @date 2022/6/17 5:14 下午 |
||||
* @desc |
||||
*/ |
||||
public class CameraManager { |
||||
|
||||
private Camera mCamera; |
||||
private int mCameraId = 0; |
||||
private SurfaceTexture mSurfaceTexture; |
||||
|
||||
public Camera getCamera() { |
||||
return mCamera; |
||||
} |
||||
|
||||
public boolean openCamera() { |
||||
return openCamera(mCameraId); |
||||
} |
||||
|
||||
public boolean openCamera(int cameraId) { |
||||
if (mCamera == null) { |
||||
try { |
||||
mCameraId = cameraId; |
||||
mCamera = Camera.open(cameraId); |
||||
setDefaultParams(); |
||||
return true; |
||||
} catch (RuntimeException e) { |
||||
return false; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
public void releaseCamera() { |
||||
if (mCamera == null) |
||||
return; |
||||
stopPreview(); |
||||
mCamera.release(); |
||||
mCamera = null; |
||||
} |
||||
|
||||
public void switchCamera() { |
||||
if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) { |
||||
mCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT; |
||||
} else { |
||||
mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK; |
||||
} |
||||
releaseCamera(); |
||||
openCamera(mCameraId); |
||||
startPreview(mSurfaceTexture); |
||||
} |
||||
|
||||
private static Camera.Size getLargePictureSize(Camera camera){ |
||||
if(camera != null){ |
||||
List<Camera.Size> sizes = camera.getParameters().getSupportedPictureSizes(); |
||||
Camera.Size temp = sizes.get(0); |
||||
for(int i = 1;i < sizes.size();i ++){ |
||||
float scale = (float)(sizes.get(i).height) / sizes.get(i).width; |
||||
if(temp.width < sizes.get(i).width && scale < 0.6f && scale > 0.5f) |
||||
temp = sizes.get(i); |
||||
} |
||||
return temp; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
private static Camera.Size getLargePreviewSize(Camera camera){ |
||||
if(camera != null){ |
||||
List<Camera.Size> sizes = camera.getParameters().getSupportedPreviewSizes(); |
||||
Camera.Size temp = sizes.get(0); |
||||
for(int i = 1;i < sizes.size();i ++){ |
||||
if(temp.width < sizes.get(i).width) |
||||
temp = sizes.get(i); |
||||
} |
||||
return temp; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public void setDefaultParams() { |
||||
Parameters parameters = mCamera.getParameters(); |
||||
if (parameters.getSupportedFocusModes().contains( |
||||
Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { |
||||
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); |
||||
} |
||||
Camera.Size previewSize = getLargePreviewSize(mCamera); |
||||
parameters.setPreviewSize(previewSize.width, previewSize.height); |
||||
Camera.Size pictureSize = getLargePictureSize(mCamera); |
||||
parameters.setPictureSize(pictureSize.width, pictureSize.height); |
||||
parameters.setRotation(90); |
||||
mCamera.setParameters(parameters); |
||||
} |
||||
|
||||
public void startPreview(SurfaceTexture surfaceTexture) { |
||||
if (mCamera == null) |
||||
return; |
||||
try { |
||||
mCamera.setPreviewTexture(surfaceTexture); |
||||
mSurfaceTexture = surfaceTexture; |
||||
mCamera.startPreview(); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
|
||||
public void stopPreview() { |
||||
if (mCamera == null) |
||||
return; |
||||
mCamera.setPreviewCallback(null); |
||||
mCamera.stopPreview(); |
||||
} |
||||
|
||||
public int getOrientation() { |
||||
Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); |
||||
Camera.getCameraInfo(mCameraId, cameraInfo); |
||||
return cameraInfo.orientation; |
||||
} |
||||
|
||||
public Camera.Size getPreviewSize() { |
||||
return mCamera.getParameters().getPreviewSize(); |
||||
} |
||||
|
||||
public boolean isFront() { |
||||
return mCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,29 @@ |
||||
package com.frank.camerafilter.factory; |
||||
|
||||
import android.content.Context; |
||||
|
||||
import com.frank.camerafilter.filter.advance.BeautyCrayonFilter; |
||||
import com.frank.camerafilter.filter.advance.BeautySketchFilter; |
||||
import com.frank.camerafilter.filter.BaseFilter; |
||||
|
||||
public class BeautyFilterFactory { |
||||
|
||||
private static BeautyFilterType filterType = BeautyFilterType.NONE; |
||||
|
||||
public static BaseFilter getFilter(BeautyFilterType type, Context context) { |
||||
filterType = type; |
||||
switch (type) { |
||||
case SKETCH: |
||||
return new BeautySketchFilter(context); |
||||
case CRAYON: |
||||
return new BeautyCrayonFilter(context); |
||||
default: |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
public static BeautyFilterType getFilterType() { |
||||
return filterType; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,7 @@ |
||||
package com.frank.camerafilter.factory; |
||||
|
||||
public enum BeautyFilterType { |
||||
NONE, |
||||
CRAYON, |
||||
SKETCH |
||||
} |
@ -0,0 +1,173 @@ |
||||
package com.frank.camerafilter.filter; |
||||
|
||||
import android.opengl.GLES30; |
||||
|
||||
import com.frank.camerafilter.util.OpenGLUtil; |
||||
import com.frank.camerafilter.util.Rotation; |
||||
import com.frank.camerafilter.util.TextureRotateUtil; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.nio.ByteOrder; |
||||
import java.nio.FloatBuffer; |
||||
import java.util.LinkedList; |
||||
|
||||
public class BaseFilter { |
||||
|
||||
public final static String NORMAL_VERTEX_SHADER = |
||||
"attribute vec4 position;\n" + |
||||
"attribute vec4 inputTextureCoordinate;\n" + |
||||
"varying vec2 textureCoordinate;\n" + |
||||
"void main() {\n" + |
||||
" gl_Position = position;\n" + |
||||
" textureCoordinate = inputTextureCoordinate.xy;\n" + |
||||
"}"; |
||||
|
||||
private final String mVertexShader; |
||||
private final String mFragmentShader; |
||||
private final LinkedList<Runnable> mRunnableDraw; |
||||
|
||||
protected int mProgramId; |
||||
protected int mInputWidth; |
||||
protected int mInputHeight; |
||||
protected int mOutputWidth; |
||||
protected int mOutputHeight; |
||||
protected int mUniformTexture; |
||||
protected int mAttributePosition; |
||||
protected int mAttributeTextureCoordinate; |
||||
protected boolean mHasInitialized; |
||||
protected FloatBuffer mVertexBuffer; |
||||
protected FloatBuffer mTextureBuffer; |
||||
|
||||
public BaseFilter(String vertexShader, String fragmentShader) { |
||||
mRunnableDraw = new LinkedList<>(); |
||||
mVertexShader = vertexShader; |
||||
mFragmentShader = fragmentShader; |
||||
|
||||
mVertexBuffer = ByteBuffer.allocateDirect(TextureRotateUtil.VERTEX.length * 4) |
||||
.order(ByteOrder.nativeOrder()) |
||||
.asFloatBuffer(); |
||||
mVertexBuffer.put(TextureRotateUtil.VERTEX).position(0); |
||||
|
||||
mTextureBuffer = ByteBuffer.allocateDirect(TextureRotateUtil.TEXTURE_ROTATE_0.length * 4) |
||||
.order(ByteOrder.nativeOrder()) |
||||
.asFloatBuffer(); |
||||
mTextureBuffer.put(TextureRotateUtil.getRotateTexture(Rotation.NORMAL, false, true)) |
||||
.position(0); |
||||
} |
||||
|
||||
protected void onInit() { |
||||
mProgramId = OpenGLUtil.loadProgram(mVertexShader, mFragmentShader); |
||||
mAttributePosition = GLES30.glGetAttribLocation(mProgramId, "position"); |
||||
mUniformTexture = GLES30.glGetUniformLocation(mProgramId, "inputImageTexture"); |
||||
mAttributeTextureCoordinate = GLES30.glGetAttribLocation(mProgramId, "inputTextureCoordinate"); |
||||
} |
||||
|
||||
protected void onInitialized() { |
||||
|
||||
} |
||||
|
||||
public void init() { |
||||
onInit(); |
||||
mHasInitialized = true; |
||||
onInitialized(); |
||||
} |
||||
|
||||
protected void onDestroy() { |
||||
|
||||
} |
||||
|
||||
public void destroy() { |
||||
mHasInitialized = false; |
||||
GLES30.glDeleteProgram(mProgramId); |
||||
onDestroy(); |
||||
} |
||||
|
||||
public void onInputSizeChanged(final int width, final int height) { |
||||
mInputWidth = width; |
||||
mInputHeight = height; |
||||
} |
||||
|
||||
protected void runPendingOnDrawTask() { |
||||
while (!mRunnableDraw.isEmpty()) { |
||||
mRunnableDraw.removeFirst().run(); |
||||
} |
||||
} |
||||
|
||||
protected void onDrawArrayBefore() { |
||||
|
||||
} |
||||
|
||||
protected void onDrawArrayAfter() { |
||||
|
||||
} |
||||
|
||||
public int onDrawFrame(final int textureId) { |
||||
return onDrawFrame(textureId, mVertexBuffer, mTextureBuffer); |
||||
} |
||||
|
||||
public int onDrawFrame(final int textureId, FloatBuffer vertexBuffer, FloatBuffer textureBuffer) { |
||||
if (!mHasInitialized) |
||||
return OpenGLUtil.NOT_INIT; |
||||
|
||||
GLES30.glUseProgram(mProgramId); |
||||
runPendingOnDrawTask(); |
||||
vertexBuffer.position(0); |
||||
GLES30.glVertexAttribPointer(mAttributePosition, 2, GLES30.GL_FLOAT, false, 0, vertexBuffer); |
||||
GLES30.glEnableVertexAttribArray(mAttributePosition); |
||||
textureBuffer.position(0); |
||||
GLES30.glVertexAttribPointer(mAttributeTextureCoordinate, 2, GLES30.GL_FLOAT, false, 0, textureBuffer); |
||||
GLES30.glEnableVertexAttribArray(mAttributeTextureCoordinate); |
||||
|
||||
if (textureId != OpenGLUtil.NO_TEXTURE) { |
||||
GLES30.glActiveTexture(GLES30.GL_TEXTURE0); |
||||
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId); |
||||
GLES30.glUniform1i(mUniformTexture, 0); |
||||
} |
||||
|
||||
onDrawArrayBefore(); |
||||
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4); |
||||
GLES30.glDisableVertexAttribArray(mAttributePosition); |
||||
GLES30.glDisableVertexAttribArray(mAttributeTextureCoordinate); |
||||
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0); |
||||
onDrawArrayAfter(); |
||||
return OpenGLUtil.ON_DRAWN; |
||||
} |
||||
|
||||
public boolean hasInitialized() { |
||||
return mHasInitialized; |
||||
} |
||||
|
||||
public int getProgramId() { |
||||
return mProgramId; |
||||
} |
||||
|
||||
protected void runOnDraw(final Runnable runnable) { |
||||
synchronized (mRunnableDraw) { |
||||
mRunnableDraw.addLast(runnable); |
||||
} |
||||
} |
||||
|
||||
public void setFloat(final int location, final float floatVal) { |
||||
runOnDraw(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
GLES30.glUniform1f(location, floatVal); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
public void setFloatVec2(final int location, final float[] floatArray) { |
||||
runOnDraw(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
GLES30.glUniform2fv(location, 1, FloatBuffer.wrap(floatArray)); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
public void onOutputSizeChanged(final int width, final int height) { |
||||
mOutputWidth = width; |
||||
mOutputHeight = height; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,155 @@ |
||||
package com.frank.camerafilter.filter; |
||||
|
||||
import android.content.Context; |
||||
import android.opengl.GLES11Ext; |
||||
import android.opengl.GLES30; |
||||
|
||||
import com.frank.camerafilter.R; |
||||
import com.frank.camerafilter.util.OpenGLUtil; |
||||
|
||||
import java.nio.FloatBuffer; |
||||
|
||||
public class BeautyCameraFilter extends BaseFilter { |
||||
|
||||
private int frameWidth = -1; |
||||
private int frameHeight = -1; |
||||
private int[] frameBuffer = null; |
||||
private int[] frameBufferTexture = null; |
||||
|
||||
private int textureTransformLocation; |
||||
private float[] textureTransformMatrix; |
||||
|
||||
public BeautyCameraFilter(Context context) { |
||||
super(OpenGLUtil.readShaderFromSource(context, R.raw.default_vertex), |
||||
OpenGLUtil.readShaderFromSource(context, R.raw.default_fragment)); |
||||
} |
||||
|
||||
protected void onInit() { |
||||
super.onInit(); |
||||
textureTransformLocation = GLES30.glGetUniformLocation(getProgramId(), "textureTransform"); |
||||
} |
||||
|
||||
public void setTextureTransformMatrix(float[] matrix) { |
||||
textureTransformMatrix = matrix; |
||||
} |
||||
|
||||
@Override |
||||
public int onDrawFrame(int textureId) { |
||||
return onDrawFrame(textureId, mVertexBuffer, mTextureBuffer); |
||||
} |
||||
|
||||
@Override |
||||
public int onDrawFrame(int textureId, FloatBuffer vertexBuffer, FloatBuffer textureBuffer) { |
||||
if (!hasInitialized()) { |
||||
return OpenGLUtil.NOT_INIT; |
||||
} |
||||
GLES30.glUseProgram(getProgramId()); |
||||
runPendingOnDrawTask(); |
||||
vertexBuffer.position(0); |
||||
GLES30.glVertexAttribPointer(mAttributePosition, 2, GLES30.GL_FLOAT, false, 0, vertexBuffer); |
||||
GLES30.glEnableVertexAttribArray(mAttributePosition); |
||||
textureBuffer.position(0); |
||||
GLES30.glVertexAttribPointer(mAttributeTextureCoordinate, 2, GLES30.GL_FLOAT, false, 0, textureBuffer); |
||||
GLES30.glEnableVertexAttribArray(mAttributeTextureCoordinate); |
||||
GLES30.glUniformMatrix4fv(textureTransformLocation, 1, false, textureTransformMatrix, 0); |
||||
|
||||
if (textureId != OpenGLUtil.NO_TEXTURE) { |
||||
GLES30.glActiveTexture(GLES30.GL_TEXTURE0); |
||||
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId); |
||||
GLES30.glUniform1i(mUniformTexture, 0); |
||||
} |
||||
|
||||
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4); |
||||
GLES30.glDisableVertexAttribArray(mAttributePosition); |
||||
GLES30.glDisableVertexAttribArray(mAttributeTextureCoordinate); |
||||
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0); |
||||
return OpenGLUtil.ON_DRAWN; |
||||
} |
||||
|
||||
public int onDrawToTexture(int textureId) { |
||||
if (!hasInitialized()) { |
||||
return OpenGLUtil.NOT_INIT; |
||||
} |
||||
if (frameBuffer == null) { |
||||
return OpenGLUtil.NO_TEXTURE; |
||||
} |
||||
GLES30.glUseProgram(getProgramId()); |
||||
runPendingOnDrawTask(); |
||||
GLES30.glViewport(0, 0, frameWidth, frameHeight); |
||||
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBuffer[0]); |
||||
|
||||
mVertexBuffer.position(0); |
||||
GLES30.glVertexAttribPointer(mAttributePosition, 2, GLES30.GL_FLOAT, false, 0, mVertexBuffer); |
||||
GLES30.glEnableVertexAttribArray(mAttributePosition); |
||||
mTextureBuffer.position(0); |
||||
GLES30.glVertexAttribPointer(mAttributeTextureCoordinate, 2, GLES30.GL_FLOAT, false, 0, mTextureBuffer); |
||||
GLES30.glEnableVertexAttribArray(mAttributeTextureCoordinate); |
||||
GLES30.glUniformMatrix4fv(textureTransformLocation, 1, false, textureTransformMatrix, 0); |
||||
|
||||
if (textureId != OpenGLUtil.NO_TEXTURE) { |
||||
GLES30.glActiveTexture(GLES30.GL_TEXTURE0); |
||||
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId); |
||||
GLES30.glUniform1i(mUniformTexture, 0); |
||||
} |
||||
|
||||
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4); |
||||
GLES30.glDisableVertexAttribArray(mAttributePosition); |
||||
GLES30.glDisableVertexAttribArray(mAttributeTextureCoordinate); |
||||
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0); |
||||
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0); |
||||
GLES30.glViewport(0, 0, mOutputWidth, mOutputHeight); |
||||
return frameBufferTexture[0]; |
||||
} |
||||
|
||||
@Override |
||||
public void onInputSizeChanged(int width, int height) { |
||||
super.onInputSizeChanged(width, height); |
||||
|
||||
} |
||||
|
||||
@Override |
||||
protected void onDestroy() { |
||||
super.onDestroy(); |
||||
destroyFrameBuffer(); |
||||
} |
||||
|
||||
public void initFrameBuffer(int width, int height) { |
||||
if (frameBuffer != null && (frameWidth != width || frameHeight != height)) |
||||
destroyFrameBuffer(); |
||||
if (frameBuffer == null) { |
||||
frameWidth = width; |
||||
frameHeight = height; |
||||
frameBuffer = new int[1]; |
||||
frameBufferTexture = new int[1]; |
||||
|
||||
GLES30.glGenFramebuffers(1, frameBuffer, 0); |
||||
GLES30.glGenTextures(1, frameBufferTexture, 0); |
||||
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, frameBufferTexture[0]); |
||||
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR); |
||||
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR); |
||||
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE); |
||||
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE); |
||||
GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, width, height, |
||||
0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null); |
||||
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBuffer[0]); |
||||
GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, |
||||
GLES30.GL_TEXTURE_2D, frameBufferTexture[0], 0); |
||||
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0); |
||||
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0); |
||||
} |
||||
} |
||||
|
||||
public void destroyFrameBuffer() { |
||||
if (frameBufferTexture != null) { |
||||
GLES30.glDeleteTextures(1, frameBufferTexture, 0); |
||||
frameBufferTexture = null; |
||||
} |
||||
if (frameBuffer != null) { |
||||
GLES30.glDeleteFramebuffers(1, frameBuffer, 0); |
||||
frameBuffer = null; |
||||
} |
||||
frameWidth = -1; |
||||
frameHeight = -1; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,42 @@ |
||||
package com.frank.camerafilter.filter.advance; |
||||
|
||||
import android.content.Context; |
||||
import android.opengl.GLES20; |
||||
|
||||
import com.frank.camerafilter.R; |
||||
import com.frank.camerafilter.filter.BaseFilter; |
||||
import com.frank.camerafilter.util.OpenGLUtil; |
||||
|
||||
public class BeautyCrayonFilter extends BaseFilter { |
||||
|
||||
// 1.0--5.0
|
||||
private int mStrengthLocation; |
||||
|
||||
private int mStepOffsetLocation; |
||||
|
||||
public BeautyCrayonFilter(Context context) { |
||||
super(NORMAL_VERTEX_SHADER, OpenGLUtil.readShaderFromSource(context, R.raw.crayon)); |
||||
} |
||||
|
||||
protected void onInit() { |
||||
super.onInit(); |
||||
mStrengthLocation = GLES20.glGetUniformLocation(getProgramId(), "strength"); |
||||
mStepOffsetLocation = GLES20.glGetUniformLocation(getProgramId(), "singleStepOffset"); |
||||
} |
||||
|
||||
protected void onInitialized() { |
||||
super.onInitialized(); |
||||
setFloat(mStrengthLocation, 2.0f); |
||||
} |
||||
|
||||
@Override |
||||
public void onInputSizeChanged(int width, int height) { |
||||
super.onInputSizeChanged(width, height); |
||||
setFloatVec2(mStepOffsetLocation, new float[] {1.0f / width, 1.0f / height}); |
||||
} |
||||
|
||||
protected void onDestroy() { |
||||
super.onDestroy(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,37 @@ |
||||
package com.frank.camerafilter.filter.advance; |
||||
|
||||
import android.content.Context; |
||||
import android.opengl.GLES20; |
||||
|
||||
import com.frank.camerafilter.R; |
||||
import com.frank.camerafilter.filter.BaseFilter; |
||||
import com.frank.camerafilter.util.OpenGLUtil; |
||||
|
||||
public class BeautySketchFilter extends BaseFilter { |
||||
|
||||
private int strengthLocation; |
||||
private int stepOffsetLocation; |
||||
|
||||
public BeautySketchFilter(Context context) { |
||||
super(NORMAL_VERTEX_SHADER, OpenGLUtil.readShaderFromSource(context, R.raw.sketch)); |
||||
} |
||||
|
||||
protected void onInit() { |
||||
super.onInit(); |
||||
strengthLocation = GLES20.glGetUniformLocation(getProgramId(), "strength"); |
||||
stepOffsetLocation = GLES20.glGetUniformLocation(getProgramId(), "singleStepOffset"); |
||||
} |
||||
|
||||
@Override |
||||
protected void onInitialized() { |
||||
super.onInitialized(); |
||||
setFloat(strengthLocation, 0.5f); |
||||
} |
||||
|
||||
@Override |
||||
public void onInputSizeChanged(int width, int height) { |
||||
super.onInputSizeChanged(width, height); |
||||
setFloatVec2(stepOffsetLocation, new float[] {1.0f / width, 1.0f / height}); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,195 @@ |
||||
package com.frank.camerafilter.recorder.gles; |
||||
|
||||
import android.graphics.SurfaceTexture; |
||||
import android.opengl.EGL14; |
||||
import android.opengl.EGLConfig; |
||||
import android.opengl.EGLContext; |
||||
import android.opengl.EGLDisplay; |
||||
import android.opengl.EGLExt; |
||||
import android.opengl.EGLSurface; |
||||
import android.util.Log; |
||||
import android.view.Surface; |
||||
|
||||
/** |
||||
* Core EGL state (display, context, config). |
||||
* <p> |
||||
* The EGLContext must only be attached to one thread at a time. This class is not thread-safe. |
||||
*/ |
||||
|
||||
public final class EglCore { |
||||
|
||||
private final static String TAG = EglCore.class.getSimpleName(); |
||||
|
||||
public final static int FLAG_RECORDABLE = 0x01; |
||||
|
||||
public final static int FLAG_TRY_GLES3 = 0x02; |
||||
|
||||
private final static int EGL_RECORDABLE_ANDROID = 0x3142; |
||||
|
||||
private int mGlVersion = -1; |
||||
private EGLConfig mEGLConfig = null; |
||||
private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY; |
||||
private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT; |
||||
|
||||
public EglCore() { |
||||
this(null, 0); |
||||
} |
||||
|
||||
public EglCore(EGLContext sharedContext, int flag) { |
||||
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); |
||||
int[] version = new int[2]; |
||||
if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { |
||||
throw new RuntimeException("unable to init EGL14"); |
||||
} |
||||
|
||||
if ((flag & FLAG_TRY_GLES3) != 0) { |
||||
initEGLContext(sharedContext, flag, 3); |
||||
} |
||||
if (mEGLContext == EGL14.EGL_NO_CONTEXT) { |
||||
initEGLContext(sharedContext, flag, 2); |
||||
} |
||||
|
||||
int[] value = new int[1]; |
||||
EGL14.eglQueryContext(mEGLDisplay, mEGLContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, value, 0); |
||||
Log.i(TAG, "EGLContext client version=" + value[0]); |
||||
} |
||||
|
||||
private void initEGLContext(EGLContext sharedContext, int flag, int version) { |
||||
EGLConfig config = getConfig(flag, version); |
||||
if (config == null) { |
||||
throw new RuntimeException("unable to find suitable EGLConfig"); |
||||
} |
||||
int[] attributeList = {EGL14.EGL_CONTEXT_CLIENT_VERSION, version, EGL14.EGL_NONE}; |
||||
EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, attributeList, 0); |
||||
if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) { |
||||
mEGLConfig = config; |
||||
mEGLContext = context; |
||||
mGlVersion = version; |
||||
} |
||||
} |
||||
|
||||
private EGLConfig getConfig(int flag, int version) { |
||||
int renderType = EGL14.EGL_OPENGL_ES2_BIT; |
||||
if (version >= 3) { |
||||
renderType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR; |
||||
} |
||||
|
||||
int[] attributeList = { |
||||
EGL14.EGL_RED_SIZE, 8, |
||||
EGL14.EGL_GREEN_SIZE, 8, |
||||
EGL14.EGL_BLUE_SIZE, 8, |
||||
EGL14.EGL_ALPHA_SIZE, 8, |
||||
//EGL14.EGL_DEPTH_SIZE, 16,
|
||||
//EGL14.EGL_STENCIL_SIZE, 8,
|
||||
EGL14.EGL_RENDERABLE_TYPE, renderType, |
||||
EGL14.EGL_NONE, 0, |
||||
EGL14.EGL_NONE |
||||
}; |
||||
|
||||
if ((flag & FLAG_RECORDABLE) != 0) { |
||||
attributeList[attributeList.length - 3] = EGL_RECORDABLE_ANDROID; |
||||
attributeList[attributeList.length - 2] = 1; |
||||
} |
||||
int[] numConfigs = new int[1]; |
||||
EGLConfig[] configs = new EGLConfig[1]; |
||||
if (!EGL14.eglChooseConfig(mEGLDisplay, attributeList, 0, configs, |
||||
0, configs.length, numConfigs, 0)) { |
||||
Log.e(TAG, "unable to find RGB8888 / " + version + " EGLConfig"); |
||||
return null; |
||||
} |
||||
return configs[0]; |
||||
} |
||||
|
||||
public void release() { |
||||
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { |
||||
EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); |
||||
EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); |
||||
EGL14.eglReleaseThread(); |
||||
EGL14.eglTerminate(mEGLDisplay); |
||||
} |
||||
mEGLConfig = null; |
||||
mEGLDisplay = EGL14.EGL_NO_DISPLAY; |
||||
mEGLContext = EGL14.EGL_NO_CONTEXT; |
||||
} |
||||
|
||||
@Override |
||||
protected void finalize() throws Throwable { |
||||
try { |
||||
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { |
||||
release(); |
||||
} |
||||
} finally { |
||||
super.finalize(); |
||||
} |
||||
} |
||||
|
||||
public void releaseSurface(EGLSurface eglSurface) { |
||||
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { |
||||
EGL14.eglDestroySurface(mEGLDisplay, eglSurface); |
||||
} |
||||
} |
||||
|
||||
public EGLSurface createWindowSurface(Object surface) { |
||||
if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture)) { |
||||
throw new RuntimeException("invalid surface:" + surface); |
||||
} |
||||
|
||||
int[] surfaceAttr = {EGL14.EGL_NONE}; |
||||
EGLSurface eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surface, surfaceAttr, 0); |
||||
if (eglSurface == null) { |
||||
throw new RuntimeException("window surface is null"); |
||||
} |
||||
return eglSurface; |
||||
} |
||||
|
||||
public EGLSurface createOffsetScreenSurface(int width, int height) { |
||||
int[] surfaceAttr = {EGL14.EGL_WIDTH, width, |
||||
EGL14.EGL_HEIGHT, height, |
||||
EGL14.EGL_NONE}; |
||||
EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, surfaceAttr, 0); |
||||
if (eglSurface == null) { |
||||
throw new RuntimeException("offset-screen surface is null"); |
||||
} |
||||
return eglSurface; |
||||
} |
||||
|
||||
public void makeCurrent(EGLSurface eglSurface) { |
||||
if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext)) { |
||||
throw new RuntimeException("eglMakeCurrent failed!"); |
||||
} |
||||
} |
||||
|
||||
public void makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) { |
||||
if (!EGL14.eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext)) { |
||||
throw new RuntimeException("eglMakeCurrent failed!"); |
||||
} |
||||
} |
||||
|
||||
public boolean swapBuffers(EGLSurface eglSurface) { |
||||
return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface); |
||||
} |
||||
|
||||
public void setPresentationTime(EGLSurface eglSurface, long nsec) { |
||||
EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsec); |
||||
} |
||||
|
||||
public boolean isCurrent(EGLSurface eglSurface) { |
||||
return mEGLContext.equals(EGL14.eglGetCurrentContext()) |
||||
&& eglSurface.equals(EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW)); |
||||
} |
||||
|
||||
public int querySurface(EGLSurface eglSurface, int what) { |
||||
int[] value = new int[1]; |
||||
EGL14.eglQuerySurface(mEGLDisplay, eglSurface, what, value, 0); |
||||
return value[0]; |
||||
} |
||||
|
||||
public String queryString(int what) { |
||||
return EGL14.eglQueryString(mEGLDisplay, what); |
||||
} |
||||
|
||||
public int getVersion() { |
||||
return mGlVersion; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,106 @@ |
||||
package com.frank.camerafilter.recorder.gles; |
||||
|
||||
import android.graphics.Bitmap; |
||||
import android.opengl.EGL14; |
||||
import android.opengl.EGLSurface; |
||||
import android.opengl.GLES20; |
||||
|
||||
import java.io.BufferedOutputStream; |
||||
import java.io.File; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
import java.nio.IntBuffer; |
||||
|
||||
/** |
||||
* @author xufulong |
||||
* @date 2022/6/23 8:51 上午 |
||||
* @desc |
||||
*/ |
||||
public class EglSurfaceBase { |
||||
|
||||
protected EglCore mEglCore; |
||||
protected int mWidth = -1; |
||||
protected int mHeight = -1; |
||||
|
||||
private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; |
||||
|
||||
protected EglSurfaceBase(EglCore eglCore) { |
||||
mEglCore = eglCore; |
||||
} |
||||
|
||||
public void createWindowSurface(Object surface) { |
||||
if (mEGLSurface != EGL14.EGL_NO_SURFACE) { |
||||
throw new IllegalStateException("egl surface has already created"); |
||||
} |
||||
mEGLSurface = mEglCore.createWindowSurface(surface); |
||||
} |
||||
|
||||
public void createOffsetScreenSurface(int width, int height) { |
||||
if (mEGLSurface != EGL14.EGL_NO_SURFACE) { |
||||
throw new IllegalStateException("egl surface has already created"); |
||||
} |
||||
mWidth = width; |
||||
mHeight = height; |
||||
mEGLSurface = mEglCore.createOffsetScreenSurface(width, height); |
||||
} |
||||
|
||||
public int getWidth() { |
||||
if (mWidth <= 0) { |
||||
mWidth = mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH); |
||||
} |
||||
return mWidth; |
||||
} |
||||
|
||||
public int getHeight() { |
||||
if (mHeight <= 0) { |
||||
mHeight = mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT); |
||||
} |
||||
return mHeight; |
||||
} |
||||
|
||||
public void releaseEglSurface() { |
||||
mEglCore.releaseSurface(mEGLSurface); |
||||
mEGLSurface = EGL14.EGL_NO_SURFACE; |
||||
mWidth = -1; |
||||
mHeight = -1; |
||||
} |
||||
|
||||
public void makeCurrent() { |
||||
mEglCore.makeCurrent(mEGLSurface); |
||||
} |
||||
|
||||
public void makeCurrentReadFrom(EglSurfaceBase readSurface) { |
||||
mEglCore.makeCurrent(mEGLSurface, readSurface.mEGLSurface); |
||||
} |
||||
|
||||
public boolean swapBuffers() { |
||||
return mEglCore.swapBuffers(mEGLSurface); |
||||
} |
||||
|
||||
public void setPresentationTime(long nsec) { |
||||
mEglCore.setPresentationTime(mEGLSurface, nsec); |
||||
} |
||||
|
||||
public void saveFrame(File file) throws IOException { |
||||
if (!mEglCore.isCurrent(mEGLSurface)) { |
||||
throw new RuntimeException("isn't current surface/context"); |
||||
} |
||||
String fileName = file.toString(); |
||||
int width = getWidth(); |
||||
int height = getHeight(); |
||||
IntBuffer buffer = IntBuffer.allocate(width * height); |
||||
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buffer); |
||||
BufferedOutputStream outputStream = null; |
||||
try { |
||||
outputStream = new BufferedOutputStream(new FileOutputStream(fileName)); |
||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); |
||||
bitmap.copyPixelsFromBuffer(buffer); |
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); |
||||
bitmap.recycle(); |
||||
} finally { |
||||
if (outputStream != null) |
||||
outputStream.close(); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,325 @@ |
||||
package com.frank.camerafilter.recorder.video; |
||||
|
||||
import android.content.Context; |
||||
import android.graphics.SurfaceTexture; |
||||
import android.opengl.EGLContext; |
||||
import android.os.Handler; |
||||
import android.os.Looper; |
||||
import android.os.Message; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.frank.camerafilter.filter.BeautyCameraFilter; |
||||
import com.frank.camerafilter.filter.BaseFilter; |
||||
import com.frank.camerafilter.factory.BeautyFilterFactory; |
||||
import com.frank.camerafilter.factory.BeautyFilterType; |
||||
import com.frank.camerafilter.recorder.gles.EglCore; |
||||
|
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.lang.ref.WeakReference; |
||||
import java.nio.FloatBuffer; |
||||
|
||||
/** |
||||
* Encode a movie from frames rendered from an external texture image. |
||||
* <p> |
||||
* The object wraps an encoder running on a dedicated thread. The various control messages |
||||
* may be sent from arbitrary threads (typically the app UI thread). The encoder thread |
||||
* manages both sides of the encoder (feeding and draining); the only external input is |
||||
* the GL texture. |
||||
* <p> |
||||
* The design is complicated slightly by the need to create an EGL context that shares state |
||||
* with a view that gets restarted if (say) the device orientation changes. When the view |
||||
* in question is a GLSurfaceView, we don't have full control over the EGL context creation |
||||
* on that side, so we have to bend a bit backwards here. |
||||
* <p> |
||||
* To use: |
||||
* <ul> |
||||
* <li>create TextureMovieEncoder object |
||||
* <li>create an EncoderConfig |
||||
* <li>call TextureMovieEncoder#startRecording() with the config |
||||
* <li>call TextureMovieEncoder#setTextureId() with the texture object that receives frames |
||||
* <li>for each frame, after latching it with SurfaceTexture#updateTexImage(), |
||||
* call TextureMovieEncoder#frameAvailable(). |
||||
* </ul> |
||||
*/ |
||||
public class TextureVideoRecorder implements Runnable { |
||||
|
||||
private final static String TAG = TextureVideoRecorder.class.getSimpleName(); |
||||
|
||||
private final static int MSG_START_RECORDING = 0; |
||||
private final static int MSG_STOP_RECORDING = 1; |
||||
private final static int MSG_FRAME_AVAILABLE = 2; |
||||
private final static int MSG_SET_TEXTURE_ID = 3; |
||||
private final static int MSG_UPDATE_SHARED_CONTEXT = 4; |
||||
private final static int MSG_QUIT_RECORDING = 5; |
||||
|
||||
private int mTextureId; |
||||
private EglCore mEglCore; |
||||
private BeautyCameraFilter mInput; |
||||
private WindowEglSurface mWindowSurface; |
||||
private VideoRecorderCore mVideoRecorder; |
||||
|
||||
// access by multiple threads
|
||||
private volatile RecorderHandler mHandler; |
||||
|
||||
private boolean mReady; |
||||
private boolean mRunning; |
||||
private Context mContext; |
||||
private BaseFilter mFilter; |
||||
private FloatBuffer glVertexBuffer; |
||||
private FloatBuffer glTextureBuffer; |
||||
|
||||
// guard ready/running
|
||||
private final Object mReadyFence = new Object(); |
||||
|
||||
private int mPreviewWidth = -1; |
||||
private int mPreviewHeight = -1; |
||||
private int mVideoWidth = -1; |
||||
private int mVideoHeight = -1; |
||||
|
||||
private BeautyFilterType type = BeautyFilterType.NONE; |
||||
|
||||
public TextureVideoRecorder(Context context) { |
||||
mContext = context; |
||||
} |
||||
|
||||
public static class RecorderConfig { |
||||
final int mWidth; |
||||
final int mHeight; |
||||
final int mBitrate; |
||||
final File mOutputFile; |
||||
final EGLContext mEglContext; |
||||
|
||||
public RecorderConfig(int width, int height, int bitrate, File outputFile, EGLContext eglContext) { |
||||
this.mWidth = width; |
||||
this.mHeight = height; |
||||
this.mBitrate = bitrate; |
||||
this.mOutputFile = outputFile; |
||||
this.mEglContext = eglContext; |
||||
} |
||||
|
||||
} |
||||
|
||||
public void startRecording(RecorderConfig config) { |
||||
synchronized (mReadyFence) { |
||||
if (mRunning) { |
||||
return; |
||||
} |
||||
mRunning = true; |
||||
new Thread(this, TAG).start(); |
||||
while (!mReady) { |
||||
try { |
||||
mReadyFence.wait(); |
||||
} catch (InterruptedException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
} |
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_START_RECORDING, config)); |
||||
} |
||||
|
||||
public void stopRecording() { |
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_RECORDING)); |
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_QUIT_RECORDING)); |
||||
} |
||||
|
||||
public boolean isRecording() { |
||||
synchronized (mReadyFence) { |
||||
return mRunning; |
||||
} |
||||
} |
||||
|
||||
public void updateSharedContext(EGLContext eglContext) { |
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_SHARED_CONTEXT, eglContext)); |
||||
} |
||||
|
||||
public void frameAvailable(SurfaceTexture surfaceTexture) { |
||||
synchronized (mReadyFence) { |
||||
if (!mReady) |
||||
return; |
||||
} |
||||
float[] transform = new float[16]; |
||||
surfaceTexture.getTransformMatrix(transform); |
||||
long timestamp = surfaceTexture.getTimestamp(); |
||||
if (timestamp == 0) { |
||||
return; |
||||
} |
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_FRAME_AVAILABLE, (int) (timestamp >> 32), (int) timestamp, transform)); |
||||
} |
||||
|
||||
public void setTextureId(int id) { |
||||
synchronized (mReadyFence) { |
||||
if (!mReady) |
||||
return; |
||||
} |
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_TEXTURE_ID, id, 0, null)); |
||||
} |
||||
|
||||
@Override |
||||
public void run() { |
||||
Looper.prepare(); |
||||
synchronized (mReadyFence) { |
||||
mHandler = new RecorderHandler(this); |
||||
mReady = true; |
||||
mReadyFence.notify(); |
||||
} |
||||
Looper.loop(); |
||||
synchronized (mReadyFence) { |
||||
mReady = false; |
||||
mRunning = false; |
||||
mHandler = null; |
||||
} |
||||
} |
||||
|
||||
private static class RecorderHandler extends Handler { |
||||
private final WeakReference<TextureVideoRecorder> mWeakRecorder; |
||||
|
||||
public RecorderHandler(TextureVideoRecorder recorder) { |
||||
mWeakRecorder = new WeakReference<>(recorder); |
||||
} |
||||
|
||||
@Override |
||||
public void handleMessage(@NonNull Message msg) { |
||||
Object obj = msg.obj; |
||||
TextureVideoRecorder recorder = mWeakRecorder.get(); |
||||
if (recorder == null) { |
||||
return; |
||||
} |
||||
|
||||
switch (msg.what) { |
||||
case MSG_START_RECORDING: |
||||
recorder.handlerStartRecording((RecorderConfig)obj); |
||||
break; |
||||
case MSG_STOP_RECORDING: |
||||
recorder.handlerStopRecording(); |
||||
break; |
||||
case MSG_FRAME_AVAILABLE: |
||||
long timestamp = (((long) msg.arg1) << 32) | |
||||
(((long) msg.arg2) & 0xffffffffL); |
||||
recorder.handleFrameAvailable((float[]) obj, timestamp); |
||||
break; |
||||
case MSG_SET_TEXTURE_ID: |
||||
recorder.handleSetTexture(msg.arg1); |
||||
break; |
||||
case MSG_UPDATE_SHARED_CONTEXT: |
||||
recorder.handleUpdateSharedContext((EGLContext)obj); |
||||
break; |
||||
case MSG_QUIT_RECORDING: |
||||
Looper.myLooper().quit(); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void handlerStartRecording(RecorderConfig config) { |
||||
prepareRecorder( |
||||
config.mEglContext, |
||||
config.mWidth, |
||||
config.mHeight, |
||||
config.mBitrate, |
||||
config.mOutputFile); |
||||
} |
||||
|
||||
private void handlerStopRecording() { |
||||
mVideoRecorder.drainEncoder(true); |
||||
releaseRecorder(); |
||||
} |
||||
|
||||
private void handleFrameAvailable(float[] transform, long timestamp) { |
||||
mVideoRecorder.drainEncoder(false); |
||||
mInput.setTextureTransformMatrix(transform); |
||||
if (mFilter == null) { |
||||
mInput.onDrawFrame(mTextureId, glVertexBuffer, glTextureBuffer); |
||||
} else { |
||||
mFilter.onDrawFrame(mTextureId, glVertexBuffer, glTextureBuffer); |
||||
} |
||||
mWindowSurface.setPresentationTime(timestamp); |
||||
mWindowSurface.swapBuffers(); |
||||
} |
||||
|
||||
private void handleSetTexture(int id) { |
||||
mTextureId = id; |
||||
} |
||||
|
||||
private void handleUpdateSharedContext(EGLContext eglContext) { |
||||
mWindowSurface.releaseEglSurface(); |
||||
mInput.destroy(); |
||||
mEglCore.release(); |
||||
|
||||
mEglCore = new EglCore(eglContext, EglCore.FLAG_RECORDABLE); |
||||
mWindowSurface.recreate(mEglCore); |
||||
mWindowSurface.makeCurrent(); |
||||
|
||||
mInput = new BeautyCameraFilter(mContext); |
||||
mInput.init(); |
||||
mFilter = BeautyFilterFactory.getFilter(type, mContext); |
||||
if (mFilter != null) { |
||||
mFilter.init(); |
||||
mFilter.onOutputSizeChanged(mVideoWidth, mVideoHeight); |
||||
mFilter.onInputSizeChanged(mPreviewWidth, mPreviewHeight); |
||||
} |
||||
} |
||||
|
||||
private void prepareRecorder(EGLContext eglContext, int width, int height, int bitrate, File file) { |
||||
try { |
||||
mVideoRecorder = new VideoRecorderCore(width, height, bitrate, file); |
||||
} catch (IOException e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
mVideoWidth = width; |
||||
mVideoHeight = height; |
||||
mEglCore = new EglCore(eglContext, EglCore.FLAG_RECORDABLE); |
||||
mWindowSurface = new WindowEglSurface(mEglCore, mVideoRecorder.getInputSurface(), true); |
||||
mWindowSurface.makeCurrent(); |
||||
|
||||
mInput = new BeautyCameraFilter(mContext); |
||||
mInput.init(); |
||||
mFilter = BeautyFilterFactory.getFilter(type, mContext); |
||||
if (mFilter != null) { |
||||
mFilter.init(); |
||||
mFilter.onOutputSizeChanged(mVideoWidth, mVideoHeight); |
||||
mFilter.onInputSizeChanged(mPreviewWidth, mPreviewHeight); |
||||
} |
||||
} |
||||
|
||||
private void releaseRecorder() { |
||||
mVideoRecorder.release(); |
||||
if (mWindowSurface != null) { |
||||
mWindowSurface.release(); |
||||
mWindowSurface = null; |
||||
} |
||||
if (mInput != null) { |
||||
mInput.destroy(); |
||||
mInput = null; |
||||
} |
||||
if (mFilter != null) { |
||||
mFilter.destroy(); |
||||
mFilter = null; |
||||
type = BeautyFilterType.NONE; |
||||
} |
||||
if (mEglCore != null) { |
||||
mEglCore.release(); |
||||
mEglCore = null; |
||||
} |
||||
} |
||||
public void setFilter(BeautyFilterType type) { |
||||
this.type = type; |
||||
} |
||||
|
||||
public void setPreviewSize(int width, int height){ |
||||
mPreviewWidth = width; |
||||
mPreviewHeight = height; |
||||
} |
||||
|
||||
public void setTextureBuffer(FloatBuffer glTextureBuffer) { |
||||
this.glTextureBuffer = glTextureBuffer; |
||||
} |
||||
|
||||
public void setCubeBuffer(FloatBuffer gLVertexBuffer) { |
||||
this.glVertexBuffer = gLVertexBuffer; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,121 @@ |
||||
package com.frank.camerafilter.recorder.video; |
||||
|
||||
import android.media.MediaCodec; |
||||
import android.media.MediaCodecInfo; |
||||
import android.media.MediaFormat; |
||||
import android.media.MediaMuxer; |
||||
import android.util.Log; |
||||
import android.view.Surface; |
||||
|
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
/** |
||||
* This class wraps up the core components used for surface-input video encoding. |
||||
* <p> |
||||
* Once created, frames are fed to the input surface. Remember to provide the presentation |
||||
* time stamp, and always call drainEncoder() before swapBuffers() to ensure that the |
||||
* producer side doesn't get backed up. |
||||
* <p> |
||||
* This class is not thread-safe, with one exception: it is valid to use the input surface |
||||
* on one thread, and drain the output on a different thread. |
||||
*/ |
||||
public class VideoRecorderCore { |
||||
|
||||
private final static String TAG = VideoRecorderCore.class.getSimpleName(); |
||||
|
||||
private final static int FRAME_RATE = 30; |
||||
private final static int IFRAME_INTERVAL = 5; |
||||
private final static String MIME_TYPE = "video/avc"; |
||||
private final static int TIMEOUT_USEC = 10000; |
||||
|
||||
private int mTrackIndex; |
||||
private boolean mMuxerStarted; |
||||
private final Surface mInputSurface; |
||||
private MediaMuxer mMediaMuxer; |
||||
private MediaCodec mVideoEncoder; |
||||
private final MediaCodec.BufferInfo mBufferInfo; |
||||
|
||||
public VideoRecorderCore(int width, int height, int bitrate, File outputFile) throws IOException { |
||||
mBufferInfo = new MediaCodec.BufferInfo(); |
||||
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, width, height); |
||||
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); |
||||
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); |
||||
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); |
||||
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL); |
||||
|
||||
mVideoEncoder = MediaCodec.createEncoderByType(MIME_TYPE); |
||||
mVideoEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); |
||||
mInputSurface = mVideoEncoder.createInputSurface(); |
||||
mVideoEncoder.start(); |
||||
|
||||
mMediaMuxer = new MediaMuxer(outputFile.toString(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); |
||||
mTrackIndex = -1; |
||||
mMuxerStarted = false; |
||||
} |
||||
|
||||
public Surface getInputSurface() { |
||||
return mInputSurface; |
||||
} |
||||
|
||||
public void drainEncoder(boolean endOfStream) { |
||||
if (endOfStream) { |
||||
mVideoEncoder.signalEndOfInputStream(); |
||||
} |
||||
|
||||
ByteBuffer[] outputBuffers = mVideoEncoder.getOutputBuffers(); |
||||
while (true) { |
||||
int encodeStatus = mVideoEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC); |
||||
if (encodeStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { |
||||
if (!endOfStream) { |
||||
break; |
||||
} |
||||
} else if (encodeStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { |
||||
outputBuffers = mVideoEncoder.getOutputBuffers(); |
||||
} else if (encodeStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { |
||||
if (mMuxerStarted) { |
||||
throw new RuntimeException("format has changed!"); |
||||
} |
||||
MediaFormat newFormat = mVideoEncoder.getOutputFormat(); |
||||
mTrackIndex = mMediaMuxer.addTrack(newFormat); |
||||
mMediaMuxer.start(); |
||||
mMuxerStarted = true; |
||||
} else if (encodeStatus < 0) { |
||||
Log.e(TAG, "error encodeStatus=" + encodeStatus); |
||||
} else { |
||||
ByteBuffer data = outputBuffers[encodeStatus]; |
||||
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { |
||||
mBufferInfo.size = 0; |
||||
} |
||||
if (mBufferInfo.size != 0) { |
||||
if (!mMuxerStarted) { |
||||
throw new RuntimeException("muxer hasn't started"); |
||||
} |
||||
data.position(mBufferInfo.offset); |
||||
data.limit(mBufferInfo.offset + mBufferInfo.size); |
||||
mMediaMuxer.writeSampleData(mTrackIndex, data, mBufferInfo); |
||||
} |
||||
mVideoEncoder.releaseOutputBuffer(encodeStatus, false); |
||||
// end of stream
|
||||
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
public void release() { |
||||
if (mVideoEncoder != null) { |
||||
mVideoEncoder.stop(); |
||||
mVideoEncoder.release(); |
||||
mVideoEncoder = null; |
||||
} |
||||
if (mMediaMuxer != null) { |
||||
mMediaMuxer.stop(); |
||||
mMediaMuxer.release(); |
||||
mMediaMuxer = null; |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,45 @@ |
||||
package com.frank.camerafilter.recorder.video; |
||||
|
||||
import android.view.Surface; |
||||
|
||||
import com.frank.camerafilter.recorder.gles.EglCore; |
||||
import com.frank.camerafilter.recorder.gles.EglSurfaceBase; |
||||
|
||||
/** |
||||
* @author xufulong |
||||
* @date 2022/6/23 9:15 上午 |
||||
* @desc |
||||
*/ |
||||
public class WindowEglSurface extends EglSurfaceBase { |
||||
|
||||
private Surface mSurface; |
||||
private boolean mReleaseSurface; |
||||
|
||||
public WindowEglSurface(EglCore eglCore, Surface surface) { |
||||
this(eglCore, surface, false); |
||||
} |
||||
|
||||
public WindowEglSurface(EglCore eglCore, Surface surface, boolean releaseSurface) { |
||||
super(eglCore); |
||||
createWindowSurface(surface); |
||||
mSurface = surface; |
||||
mReleaseSurface = releaseSurface; |
||||
} |
||||
|
||||
public void release() { |
||||
releaseEglSurface(); |
||||
if (mSurface != null && mReleaseSurface) { |
||||
mSurface.release(); |
||||
} |
||||
mSurface = null; |
||||
} |
||||
|
||||
public void recreate(EglCore newEglCore) { |
||||
if (mSurface == null) { |
||||
throw new RuntimeException("Surface is null"); |
||||
} |
||||
mEglCore = newEglCore; |
||||
createWindowSurface(mSurface); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,128 @@ |
||||
package com.frank.camerafilter.util; |
||||
|
||||
import android.content.Context; |
||||
import android.content.res.AssetManager; |
||||
import android.graphics.Bitmap; |
||||
import android.graphics.BitmapFactory; |
||||
import android.opengl.GLES11Ext; |
||||
import android.opengl.GLES30; |
||||
import android.opengl.GLUtils; |
||||
import android.util.Log; |
||||
|
||||
import java.io.BufferedReader; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.io.InputStreamReader; |
||||
|
||||
import javax.microedition.khronos.opengles.GL10; |
||||
|
||||
public class OpenGLUtil { |
||||
|
||||
public final static int ON_DRAWN = 1; |
||||
public static final int NOT_INIT = -1; |
||||
public static final int NO_SHADER = 0; |
||||
public static final int NO_TEXTURE = -1; |
||||
|
||||
private static Bitmap getBitmapFromAssetFile(Context context, String name) { |
||||
try { |
||||
AssetManager assetManager = context.getResources().getAssets(); |
||||
InputStream stream = assetManager.open(name); |
||||
Bitmap bitmap = BitmapFactory.decodeStream(stream); |
||||
stream.close(); |
||||
return bitmap; |
||||
} catch (IOException e) { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
public static int loadTexture(final Context context, final String name) { |
||||
if (context == null || name == null) |
||||
return NO_TEXTURE; |
||||
final int[] textures = new int[1]; |
||||
GLES30.glGenTextures(1, textures, 0); |
||||
if (textures[0] == 0) |
||||
return NO_TEXTURE; |
||||
Bitmap bitmap = getBitmapFromAssetFile(context, name); |
||||
if (bitmap == null) |
||||
return NO_TEXTURE; |
||||
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]); |
||||
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR); |
||||
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR); |
||||
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE); |
||||
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE); |
||||
GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, bitmap, 0); |
||||
bitmap.recycle(); |
||||
return textures[0]; |
||||
} |
||||
|
||||
private static int loadShader(final String source, final int type) { |
||||
int shader = GLES30.glCreateShader(type); |
||||
GLES30.glShaderSource(shader, source); |
||||
GLES30.glCompileShader(shader); |
||||
int[] compile = new int[1]; |
||||
GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compile, 0); |
||||
if (compile[0] <= 0) { |
||||
Log.e("OpenGlUtil", "Shader compile error=" + GLES30.glGetShaderInfoLog(shader)); |
||||
return NO_SHADER; |
||||
} |
||||
return shader; |
||||
} |
||||
|
||||
public static int loadProgram(final String vertexSource, final String fragmentSource) { |
||||
int vertexShader = loadShader(vertexSource, GLES30.GL_VERTEX_SHADER); |
||||
int fragmentShader = loadShader(fragmentSource, GLES30.GL_FRAGMENT_SHADER); |
||||
if (vertexShader == NO_SHADER || fragmentShader == NO_SHADER) { |
||||
return 0; |
||||
} |
||||
int programId = GLES30.glCreateProgram(); |
||||
GLES30.glAttachShader(programId, vertexShader); |
||||
GLES30.glAttachShader(programId, fragmentShader); |
||||
GLES30.glLinkProgram(programId); |
||||
int[] linked = new int[1]; |
||||
GLES30.glGetProgramiv(programId, GLES30.GL_LINK_STATUS, linked, 0); |
||||
if (linked[0] <= 0) { |
||||
programId = 0; |
||||
Log.e("OpenGlUtil", "program link error=" + GLES30.glGetProgramInfoLog(programId)); |
||||
} |
||||
GLES30.glDeleteShader(vertexShader); |
||||
GLES30.glDeleteShader(fragmentShader); |
||||
return programId; |
||||
} |
||||
|
||||
public static int getExternalOESTextureId() { |
||||
int[] textures = new int[1]; |
||||
GLES30.glGenTextures(1, textures, 0); |
||||
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0]); |
||||
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR); |
||||
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); |
||||
GLES30.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE); |
||||
GLES30.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE); |
||||
return textures[0]; |
||||
} |
||||
|
||||
public static String readShaderFromSource(Context context, final int resourceId) { |
||||
String line; |
||||
StringBuilder builder = new StringBuilder(); |
||||
InputStream inputStream = context.getResources().openRawResource(resourceId); |
||||
InputStreamReader reader = new InputStreamReader(inputStream); |
||||
BufferedReader bufferedReader = new BufferedReader(reader); |
||||
try { |
||||
while ((line = bufferedReader.readLine()) != null) { |
||||
builder.append(line).append("\n"); |
||||
} |
||||
} catch (IOException e) { |
||||
return null; |
||||
} finally { |
||||
try { |
||||
inputStream.close(); |
||||
reader.close(); |
||||
bufferedReader.close(); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
|
||||
} |
||||
return builder.toString(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,36 @@ |
||||
package com.frank.camerafilter.util; |
||||
|
||||
public enum Rotation { |
||||
|
||||
NORMAL, ROTATION_90, ROTATION_180, ROTATION_270; |
||||
|
||||
public int toInt() { |
||||
switch (this) { |
||||
case NORMAL: |
||||
return 0; |
||||
case ROTATION_90: |
||||
return 90; |
||||
case ROTATION_180: |
||||
return 180; |
||||
case ROTATION_270: |
||||
return 270; |
||||
default: |
||||
throw new IllegalStateException("unknown rotation value..."); |
||||
} |
||||
} |
||||
|
||||
public static Rotation fromInt(int rotation) { |
||||
switch (rotation) { |
||||
case 0: |
||||
return NORMAL; |
||||
case 90: |
||||
return ROTATION_90; |
||||
case 180: |
||||
return ROTATION_180; |
||||
case 270: |
||||
return ROTATION_270; |
||||
default: |
||||
throw new IllegalStateException("unknown rotation=" +rotation); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,82 @@ |
||||
package com.frank.camerafilter.util; |
||||
|
||||
public class TextureRotateUtil { |
||||
|
||||
public final static float[] TEXTURE_ROTATE_0 = { |
||||
0.0f, 1.0f, |
||||
1.0f, 1.0f, |
||||
0.0f, 0.0f, |
||||
1.0f, 0.0f |
||||
}; |
||||
|
||||
public final static float[] TEXTURE_ROTATE_90 = { |
||||
1.0f, 1.0f, |
||||
1.0f, 0.0f, |
||||
0.0f, 1.0f, |
||||
0.0f, 0.0f |
||||
}; |
||||
|
||||
public final static float[] TEXTURE_ROTATE_180 = { |
||||
1.0f, 0.0f, |
||||
0.0f, 0.0f, |
||||
1.0f, 1.0f, |
||||
0.0f, 1.0f |
||||
}; |
||||
|
||||
public final static float[] TEXTURE_ROTATE_270 = { |
||||
0.0f, 0.0f, |
||||
0.0f, 1.0f, |
||||
1.0f, 0.0f, |
||||
1.0f, 1.0f |
||||
}; |
||||
|
||||
public final static float[] VERTEX = { |
||||
-1.0f, -1.0f, |
||||
1.0f, -1.0f, |
||||
-1.0f, 1.0f, |
||||
1.0f, 1.0f |
||||
}; |
||||
|
||||
private TextureRotateUtil() {} |
||||
|
||||
private static float flip(float value) { |
||||
return value == 1.0f ? 0.0f : 1.0f; |
||||
} |
||||
|
||||
public static float[] getRotateTexture(Rotation rotation, boolean horizontalFlip, boolean verticalFlip) { |
||||
float[] rotateTexture; |
||||
switch (rotation) { |
||||
case ROTATION_90: |
||||
rotateTexture = TEXTURE_ROTATE_90; |
||||
break; |
||||
case ROTATION_180: |
||||
rotateTexture = TEXTURE_ROTATE_180; |
||||
break; |
||||
case ROTATION_270: |
||||
rotateTexture = TEXTURE_ROTATE_270; |
||||
break; |
||||
case NORMAL: |
||||
default: |
||||
rotateTexture = TEXTURE_ROTATE_0; |
||||
break; |
||||
} |
||||
if (horizontalFlip) { |
||||
rotateTexture = new float[] { |
||||
flip(rotateTexture[0]), rotateTexture[1], |
||||
flip(rotateTexture[2]), rotateTexture[3], |
||||
flip(rotateTexture[4]), rotateTexture[5], |
||||
flip(rotateTexture[6]), rotateTexture[7] |
||||
}; |
||||
} |
||||
if (verticalFlip) { |
||||
rotateTexture = new float[] { |
||||
rotateTexture[0], flip(rotateTexture[1]), |
||||
rotateTexture[2], flip(rotateTexture[3]), |
||||
rotateTexture[4], flip(rotateTexture[5]), |
||||
rotateTexture[6], flip(rotateTexture[7]) |
||||
}; |
||||
} |
||||
return rotateTexture; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,50 @@ |
||||
package com.frank.camerafilter.widget; |
||||
|
||||
import android.content.Context; |
||||
import android.opengl.GLSurfaceView; |
||||
import android.util.AttributeSet; |
||||
import android.view.SurfaceHolder; |
||||
|
||||
import com.frank.camerafilter.factory.BeautyFilterType; |
||||
|
||||
public class BeautyCameraView extends GLSurfaceView { |
||||
|
||||
private final CameraRender mCameraRender; |
||||
|
||||
public BeautyCameraView(Context context) { |
||||
this(context, null); |
||||
} |
||||
|
||||
public BeautyCameraView(Context context, AttributeSet attrs) { |
||||
super(context, attrs); |
||||
getHolder().addCallback(this); |
||||
|
||||
mCameraRender = new CameraRender(this); |
||||
setEGLContextClientVersion(3); |
||||
setRenderer(mCameraRender); |
||||
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); |
||||
} |
||||
|
||||
@Override |
||||
public void surfaceDestroyed(SurfaceHolder holder) { |
||||
super.surfaceDestroyed(holder); |
||||
if (mCameraRender != null) { |
||||
mCameraRender.releaseCamera(); |
||||
} |
||||
} |
||||
|
||||
public void switchCamera() { |
||||
if (mCameraRender != null) { |
||||
mCameraRender.switchCamera(); |
||||
} |
||||
} |
||||
|
||||
public void setFilter(BeautyFilterType type) { |
||||
mCameraRender.setFilter(type); |
||||
} |
||||
|
||||
public void setRecording(boolean isRecording) { |
||||
mCameraRender.setRecording(isRecording); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,246 @@ |
||||
package com.frank.camerafilter.widget; |
||||
|
||||
import android.graphics.SurfaceTexture; |
||||
import android.hardware.Camera; |
||||
import android.opengl.EGL14; |
||||
import android.opengl.GLES30; |
||||
import android.opengl.GLSurfaceView; |
||||
import android.os.Environment; |
||||
|
||||
import com.frank.camerafilter.camera.CameraManager; |
||||
import com.frank.camerafilter.filter.BeautyCameraFilter; |
||||
import com.frank.camerafilter.filter.BaseFilter; |
||||
import com.frank.camerafilter.factory.BeautyFilterFactory; |
||||
import com.frank.camerafilter.factory.BeautyFilterType; |
||||
import com.frank.camerafilter.recorder.video.TextureVideoRecorder; |
||||
import com.frank.camerafilter.util.OpenGLUtil; |
||||
import com.frank.camerafilter.util.Rotation; |
||||
import com.frank.camerafilter.util.TextureRotateUtil; |
||||
|
||||
import java.io.File; |
||||
import java.nio.ByteBuffer; |
||||
import java.nio.ByteOrder; |
||||
import java.nio.FloatBuffer; |
||||
|
||||
import javax.microedition.khronos.egl.EGLConfig; |
||||
import javax.microedition.khronos.opengles.GL10; |
||||
|
||||
public class CameraRender implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener { |
||||
|
||||
protected BaseFilter mFilter; |
||||
|
||||
private SurfaceTexture surfaceTexture; |
||||
private BeautyCameraFilter cameraFilter; |
||||
|
||||
private final CameraManager cameraManager; |
||||
|
||||
protected int mTextureId = OpenGLUtil.NO_TEXTURE; |
||||
|
||||
protected FloatBuffer mVertexBuffer; |
||||
|
||||
protected FloatBuffer mTextureBuffer; |
||||
|
||||
protected int mImageWidth, mImageHeight; |
||||
|
||||
protected int mSurfaceWidth, mSurfaceHeight; |
||||
private final float[] mMatrix = new float[16]; |
||||
|
||||
private final BeautyCameraView mCameraView; |
||||
|
||||
private final File outputFile; |
||||
private int recordStatus; |
||||
protected boolean recordEnable; |
||||
private final TextureVideoRecorder videoRecorder; |
||||
|
||||
private final static int RECORDING_OFF = 0; |
||||
private final static int RECORDING_ON = 1; |
||||
private final static int RECORDING_RESUME = 2; |
||||
|
||||
private static final int videoBitrate = 6 * 1024 * 1024; |
||||
private static final String videoName = "camera_record.mp4"; |
||||
private static final String videoPath = Environment.getExternalStorageDirectory().getPath(); |
||||
|
||||
public CameraRender(BeautyCameraView cameraView) { |
||||
mCameraView = cameraView; |
||||
|
||||
cameraManager = new CameraManager(); |
||||
mVertexBuffer = ByteBuffer.allocateDirect(TextureRotateUtil.VERTEX.length * 4) |
||||
.order(ByteOrder.nativeOrder()) |
||||
.asFloatBuffer(); |
||||
mVertexBuffer.put(TextureRotateUtil.VERTEX).position(0); |
||||
mTextureBuffer = ByteBuffer.allocateDirect(TextureRotateUtil.TEXTURE_ROTATE_0.length * 4) |
||||
.order(ByteOrder.nativeOrder()) |
||||
.asFloatBuffer(); |
||||
mTextureBuffer.put(TextureRotateUtil.TEXTURE_ROTATE_0).position(0); |
||||
|
||||
recordEnable = false; |
||||
recordStatus = RECORDING_OFF; |
||||
videoRecorder = new TextureVideoRecorder(mCameraView.getContext()); |
||||
outputFile = new File(videoPath, videoName); |
||||
} |
||||
|
||||
private void openCamera() { |
||||
if (cameraManager.getCamera() == null) |
||||
cameraManager.openCamera(); |
||||
Camera.Size size = cameraManager.getPreviewSize(); |
||||
if (cameraManager.getOrientation() == 90 || cameraManager.getOrientation() == 270) { |
||||
mImageWidth = size.height; |
||||
mImageHeight = size.width; |
||||
} else { |
||||
mImageWidth = size.width; |
||||
mImageHeight = size.height; |
||||
} |
||||
cameraFilter.onInputSizeChanged(mImageWidth, mImageHeight); |
||||
adjustSize(cameraManager.getOrientation(), cameraManager.isFront(), true); |
||||
} |
||||
|
||||
@Override |
||||
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) { |
||||
GLES30.glDisable(GL10.GL_DITHER); |
||||
GLES30.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
||||
GLES30.glEnable(GL10.GL_CULL_FACE); |
||||
GLES30.glEnable(GL10.GL_DEPTH_TEST); |
||||
|
||||
cameraFilter = new BeautyCameraFilter(mCameraView.getContext()); |
||||
cameraFilter.init(); |
||||
mTextureId = OpenGLUtil.getExternalOESTextureId(); |
||||
if (mTextureId != OpenGLUtil.NO_TEXTURE) { |
||||
surfaceTexture = new SurfaceTexture(mTextureId); |
||||
surfaceTexture.setOnFrameAvailableListener(this); |
||||
} |
||||
|
||||
openCamera(); |
||||
} |
||||
|
||||
@Override |
||||
public void onSurfaceChanged(GL10 gl10, int width, int height) { |
||||
GLES30.glViewport(0, 0, width, height); |
||||
mSurfaceWidth = width; |
||||
mSurfaceHeight = height; |
||||
cameraManager.startPreview(surfaceTexture); |
||||
onFilterChanged(); |
||||
} |
||||
|
||||
@Override |
||||
public void onDrawFrame(GL10 gl10) { |
||||
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT); |
||||
GLES30.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
||||
|
||||
surfaceTexture.updateTexImage(); |
||||
|
||||
surfaceTexture.getTransformMatrix(mMatrix); |
||||
cameraFilter.setTextureTransformMatrix(mMatrix); |
||||
int id = mTextureId; |
||||
if (mFilter == null) { |
||||
cameraFilter.onDrawFrame(mTextureId, mVertexBuffer, mTextureBuffer); |
||||
} else { |
||||
id = cameraFilter.onDrawToTexture(mTextureId); |
||||
mFilter.onDrawFrame(id, mVertexBuffer, mTextureBuffer); |
||||
} |
||||
|
||||
onRecordVideo(id); |
||||
} |
||||
|
||||
@Override |
||||
public void onFrameAvailable(SurfaceTexture surfaceTexture) { |
||||
mCameraView.requestRender(); |
||||
} |
||||
|
||||
public void adjustSize(int rotation, boolean horizontalFlip, boolean verticalFlip) { |
||||
float[] vertexData = TextureRotateUtil.VERTEX; |
||||
float[] textureData = TextureRotateUtil.getRotateTexture(Rotation.fromInt(rotation), |
||||
horizontalFlip, verticalFlip); |
||||
|
||||
mVertexBuffer.clear(); |
||||
mVertexBuffer.put(vertexData).position(0); |
||||
mTextureBuffer.clear(); |
||||
mTextureBuffer.put(textureData).position(0); |
||||
|
||||
} |
||||
|
||||
public void switchCamera() { |
||||
if (cameraManager != null) { |
||||
cameraManager.switchCamera(); |
||||
} |
||||
} |
||||
|
||||
public void releaseCamera() { |
||||
if (cameraManager != null) { |
||||
cameraManager.releaseCamera(); |
||||
} |
||||
} |
||||
|
||||
private void onRecordVideo(int textureId) { |
||||
if (recordEnable) { |
||||
switch (recordStatus) { |
||||
case RECORDING_OFF: |
||||
videoRecorder.setPreviewSize(mImageWidth, mImageHeight); |
||||
videoRecorder.setTextureBuffer(mTextureBuffer); |
||||
videoRecorder.setCubeBuffer(mVertexBuffer); |
||||
videoRecorder.startRecording(new TextureVideoRecorder.RecorderConfig( |
||||
mImageWidth, |
||||
mImageHeight, |
||||
videoBitrate, |
||||
outputFile, |
||||
EGL14.eglGetCurrentContext())); |
||||
recordStatus = RECORDING_ON; |
||||
break; |
||||
case RECORDING_RESUME: |
||||
videoRecorder.updateSharedContext(EGL14.eglGetCurrentContext()); |
||||
recordStatus = RECORDING_ON; |
||||
break; |
||||
case RECORDING_ON: |
||||
break; |
||||
default: |
||||
throw new RuntimeException("unknown status " + recordStatus); |
||||
} |
||||
} else { |
||||
switch (recordStatus) { |
||||
case RECORDING_ON: |
||||
case RECORDING_RESUME: |
||||
videoRecorder.stopRecording(); |
||||
recordStatus = RECORDING_OFF; |
||||
break; |
||||
case RECORDING_OFF: |
||||
break; |
||||
default: |
||||
throw new RuntimeException("unknown status " + recordStatus); |
||||
} |
||||
} |
||||
videoRecorder.setTextureId(textureId); |
||||
videoRecorder.frameAvailable(surfaceTexture); |
||||
} |
||||
|
||||
public void setRecording(boolean isRecording) { |
||||
recordEnable = isRecording; |
||||
} |
||||
|
||||
public void setFilter(final BeautyFilterType type) { |
||||
mCameraView.queueEvent(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
if (mFilter != null) |
||||
mFilter.destroy(); |
||||
mFilter = null; |
||||
mFilter = BeautyFilterFactory.getFilter(type, mCameraView.getContext()); |
||||
if (mFilter != null) |
||||
mFilter.init(); |
||||
onFilterChanged(); |
||||
} |
||||
}); |
||||
mCameraView.requestRender(); |
||||
} |
||||
|
||||
public void onFilterChanged() { |
||||
if (mFilter != null) { |
||||
mFilter.onInputSizeChanged(mImageWidth, mImageHeight); |
||||
mFilter.onOutputSizeChanged(mSurfaceWidth, mSurfaceHeight); |
||||
} |
||||
cameraFilter.onOutputSizeChanged(mSurfaceWidth, mSurfaceHeight); |
||||
if (mFilter != null) |
||||
cameraFilter.initFrameBuffer(mImageWidth, mImageHeight); |
||||
else |
||||
cameraFilter.destroyFrameBuffer(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,53 @@ |
||||
varying highp vec2 textureCoordinate; |
||||
precision mediump float; |
||||
|
||||
uniform sampler2D inputImageTexture; |
||||
uniform vec2 singleStepOffset; |
||||
uniform float strength; |
||||
|
||||
const highp vec3 W = vec3(0.299,0.587,0.114); |
||||
|
||||
const mat3 rgb2yiqMatrix = mat3( |
||||
0.299, 0.587, 0.114, |
||||
0.596,-0.275,-0.321, |
||||
0.212,-0.523, 0.311); |
||||
|
||||
const mat3 yiq2rgbMatrix = mat3( |
||||
1.0, 0.956, 0.621, |
||||
1.0,-0.272,-1.703, |
||||
1.0,-1.106, 0.0); |
||||
|
||||
|
||||
void main() |
||||
{ |
||||
vec4 oralColor = texture2D(inputImageTexture, textureCoordinate); |
||||
|
||||
vec3 maxValue = vec3(0.,0.,0.); |
||||
|
||||
for(int i = -2; i<=2; i++) |
||||
{ |
||||
for(int j = -2; j<=2; j++) |
||||
{ |
||||
vec4 tempColor = texture2D(inputImageTexture, textureCoordinate+singleStepOffset*vec2(i,j)); |
||||
maxValue.r = max(maxValue.r,tempColor.r); |
||||
maxValue.g = max(maxValue.g,tempColor.g); |
||||
maxValue.b = max(maxValue.b,tempColor.b); |
||||
} |
||||
} |
||||
|
||||
vec3 textureColor = oralColor.rgb / maxValue; |
||||
|
||||
float gray = dot(textureColor, W); |
||||
float k = 0.223529; |
||||
float alpha = min(gray,k)/k; |
||||
|
||||
textureColor = textureColor * alpha + (1.-alpha)*oralColor.rgb; |
||||
|
||||
vec3 yiqColor = textureColor * rgb2yiqMatrix; |
||||
|
||||
yiqColor.r = max(0.0,min(1.0,pow(gray,strength))); |
||||
|
||||
textureColor = yiqColor * yiq2rgbMatrix; |
||||
|
||||
gl_FragColor = vec4(textureColor, oralColor.w); |
||||
} |
@ -0,0 +1,13 @@ |
||||
#extension GL_OES_EGL_image_external : require |
||||
|
||||
precision mediump float; |
||||
|
||||
varying mediump vec2 textureCoordinate; |
||||
|
||||
uniform samplerExternalOES inputImageTexture; |
||||
|
||||
void main(){ |
||||
|
||||
vec3 centralColor = texture2D(inputImageTexture, textureCoordinate).rgb; |
||||
gl_FragColor = vec4(centralColor.rgb,1.0);; |
||||
} |
@ -0,0 +1,11 @@ |
||||
attribute vec4 position; |
||||
attribute vec4 inputTextureCoordinate; |
||||
|
||||
uniform mat4 textureTransform; |
||||
varying vec2 textureCoordinate; |
||||
|
||||
void main() |
||||
{ |
||||
textureCoordinate = (textureTransform * inputTextureCoordinate).xy; |
||||
gl_Position = position; |
||||
} |
@ -0,0 +1,46 @@ |
||||
varying highp vec2 textureCoordinate; |
||||
precision mediump float; |
||||
|
||||
uniform sampler2D inputImageTexture; |
||||
uniform vec2 singleStepOffset; |
||||
uniform float strength; |
||||
|
||||
const highp vec3 W = vec3(0.299,0.587,0.114); |
||||
|
||||
|
||||
void main() |
||||
{ |
||||
float threshold = 0.0; |
||||
//pic1 |
||||
vec4 oralColor = texture2D(inputImageTexture, textureCoordinate); |
||||
|
||||
//pic2 |
||||
vec3 maxValue = vec3(0.,0.,0.); |
||||
|
||||
for(int i = -2; i<=2; i++) |
||||
{ |
||||
for(int j = -2; j<=2; j++) |
||||
{ |
||||
vec4 tempColor = texture2D(inputImageTexture, textureCoordinate+singleStepOffset*vec2(i,j)); |
||||
maxValue.r = max(maxValue.r,tempColor.r); |
||||
maxValue.g = max(maxValue.g,tempColor.g); |
||||
maxValue.b = max(maxValue.b,tempColor.b); |
||||
threshold += dot(tempColor.rgb, W); |
||||
} |
||||
} |
||||
//pic3 |
||||
float gray1 = dot(oralColor.rgb, W); |
||||
|
||||
//pic4 |
||||
float gray2 = dot(maxValue, W); |
||||
|
||||
//pic5 |
||||
float contour = gray1 / gray2; |
||||
|
||||
threshold = threshold / 25.; |
||||
float alpha = max(1.0,gray1>threshold?1.0:(gray1/threshold)); |
||||
|
||||
float result = contour * alpha + (1.0-alpha)*gray1; |
||||
|
||||
gl_FragColor = vec4(vec3(result,result,result), oralColor.w); |
||||
} |
Loading…
Reference in new issue