在https://blog.csdn.net/llxyy299/article/details/82659683这篇文章中记录了自己创建EGL环境,自己实现一个GLSurfaceView,但是还没有记录怎么用,这篇文章来记录怎么使用自定义的GLSurfaceView。
1、首先创建一个CameraHelper类,里面封装了安卓相机的一些api:
package com.leilu.mycamera.camera;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.util.Log;
import java.io.IOException;
import java.util.List;
/**
* Created by ll on 2018/9/13.
*/
public class CameraHelper {
private Camera mCamera;
private int mCameraId = 0;
private int mDesireWidth, mDesireHeight;
private boolean mIsInit;
private SurfaceTexture mSurfaceTexture;
private boolean mIsPortrait = true;
public CameraHelper() {
}
public boolean init(int cameraId, int desireWidth, int desireHeight, SurfaceTexture surfaceTexture) {
if (mCamera == null) {
checkCameraID(cameraId);
mCameraId = cameraId;
mDesireWidth = desireWidth;
mDesireHeight = desireHeight;
mSurfaceTexture = surfaceTexture;
mCamera = Camera.open(mCameraId);
Camera.Parameters parameters = mCamera.getParameters();
Camera.Size picSize = getFitSize(desireWidth, desireHeight, parameters.getSupportedPictureSizes());
if (picSize != null) {
parameters.setPictureSize(picSize.width, picSize.height);
}
Log.i("==", "picSize:" + picSize.width + " " + picSize.height);
Camera.Size preSize = getFitSize(desireWidth, desireHeight, parameters.getSupportedPreviewSizes());
if (preSize != null) {
parameters.setPreviewSize(preSize.width, preSize.height);
}
Log.i("==", "preSize:" + preSize.width + " " + preSize.height);
mCamera.setParameters(parameters);
try {
mCamera.setPreviewTexture(surfaceTexture);
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
mIsInit = true;
return true;
}
public boolean startPreview() {
if (!mIsInit || mCamera == null) {
return false;
}
mCamera.startPreview();
return true;
}
public boolean stopPreview() {
if (!mIsInit || mCamera == null) {
return false;
}
mCamera.stopPreview();
return true;
}
public boolean switchCamera(int cameraId) {
release();
if (!init(cameraId, mDesireWidth, mDesireHeight, mSurfaceTexture)) {
return false;
}
startPreview();
return true;
}
private Camera.Size getFitSize(int desireWidth, int desireHeight,
List<Camera.Size> preSizeList) {
int reqTmpWidth;
int reqTmpHeight;
// 当屏幕为垂直的时候需要把宽高值进行调换,保证宽大于高
if (mIsPortrait) {
reqTmpWidth = desireHeight;
reqTmpHeight = desireWidth;
} else {
reqTmpWidth = desireWidth;
reqTmpHeight = desireHeight;
}
//先查找preview中是否存在与控件相同宽高的尺寸
for (Camera.Size size : preSizeList) {
if ((size.width == reqTmpWidth) && (size.height == reqTmpHeight)) {
return size;
}
}
// 得到与传入的宽高比最接近的size
float reqRatio = ((float) reqTmpWidth) / reqTmpHeight;
float curRatio, deltaRatio;
float deltaRatioMin = Float.MAX_VALUE;
Camera.Size retSize = null;
for (Camera.Size size : preSizeList) {
curRatio = ((float) size.width) / size.height;
deltaRatio = Math.abs(reqRatio - curRatio);
if (deltaRatio < deltaRatioMin) {
deltaRatioMin = deltaRatio;
retSize = size;
}
}
return retSize;
}
private void checkCameraID(int cameraId) {
int count = Camera.getNumberOfCameras();
if (cameraId < 0 || cameraId >= count) {
throw new IllegalArgumentException("Nonsupport cameraID !");
}
}
public void release() {
stopPreview();
if (mCamera != null) {
mCamera.release();
mCamera = null;
}
mIsInit = false;
}
}
2、 创建一个CameraRender类来实现Opengl ES的渲染代码:
package com.leilu.mycamera.camera;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.Matrix;
import com.leilu.floatwindow.R;
import com.leilu.floatwindow.ShaderUtil;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
/**
* Created by ll on 2018/9/13.
*/
public class CameraRender {
private static final float[] VERTEX_POSITION = {
-1, -1,
1, -1,
-1, 1,
1, 1
};
private static final float[] FRAGMENT_POSITION = {
0, 1,
1, 1,
0, 0,
1, 0
};
private SurfaceTexture mSurfaceTexture;
private int mProgram;
private int mExtTextureHandle;
private int mTexturePositionHandle;
private int mVertexPositionHandle;
private int mProjectionMatrixHandle;
private int[] mVertexBufferIds;
private int[] mFragmentBufferIds;
private int[] mExtTextureIds;
private float[] mProjectionMatrix = new float[16];
private Context mContext;
public CameraRender(Context context) {
mContext = context.getApplicationContext();
Matrix.setIdentityM(mProjectionMatrix, 0);
}
public void init(SurfaceTexture.OnFrameAvailableListener onFrameAvailable) {
if (mSurfaceTexture == null) {
mProgram = ShaderUtil.createProgram(ShaderUtil.getRawResource(mContext, R.raw.camera_vertex_shader),
ShaderUtil.getRawResource(mContext, R.raw.camera_fragment_shader));
if (mProgram == -1) {
return;
}
GLES20.glUseProgram(mProgram);
// 获取纹理句柄
mExtTextureHandle = GLES20.glGetAttribLocation(mProgram, "sTexture");
mTexturePositionHandle = GLES20.glGetAttribLocation(mProgram, "texturePosition");
mVertexPositionHandle = GLES20.glGetAttribLocation(mProgram, "vertexPosition");
mProjectionMatrixHandle = GLES20.glGetUniformLocation(mProgram, "projectionMatrix");
// 创建vbo
mVertexBufferIds = createVBO(VERTEX_POSITION);
mFragmentBufferIds = createVBO(FRAGMENT_POSITION);
// 创建纹理
mExtTextureIds = createTexture();
mSurfaceTexture = new SurfaceTexture(mExtTextureIds[0]);
mSurfaceTexture.setOnFrameAvailableListener(onFrameAvailable);
}
}
private int[] createVBO(float[] datas) {
FloatBuffer floatBuffer = ByteBuffer.allocateDirect(datas.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
floatBuffer.put(datas);
floatBuffer.position(0);
int[] vbo = new int[1];
GLES20.glGenBuffers(vbo.length, vbo, 0);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo[0]);
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, datas.length * 4, null, GLES20.GL_STATIC_DRAW);
GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, datas.length * 4, floatBuffer);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
floatBuffer.clear();
return vbo;
}
private int[] createTexture() {
int[] textures = new int[1];
GLES20.glGenTextures(textures.length, textures, 0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0]);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
return textures;
}
public void setViewPort(int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
public void setProjectionMatrix(boolean isBack) {
Matrix.setIdentityM(mProjectionMatrix, 0);
if (isBack) {
Matrix.setRotateM(mProjectionMatrix, 0, 270, 0, 0, 1);
} else {
Matrix.setRotateM(mProjectionMatrix, 0, 90, 0, 0, 1);
}
}
public void draw() {
if (mProgram == -1) {
return;
}
mSurfaceTexture.updateTexImage();
// 矩阵
GLES20.glUniformMatrix4fv(mProjectionMatrixHandle, 1, false, mProjectionMatrix, 0);
// 绑定顶点坐标
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVertexBufferIds[0]);
GLES20.glEnableVertexAttribArray(mVertexPositionHandle);
GLES20.glVertexAttribPointer(mVertexPositionHandle, 2, GLES20.GL_FLOAT, false, 0, 0);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
// 绑定片元坐标
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mFragmentBufferIds[0]);
GLES20.glEnableVertexAttribArray(mTexturePositionHandle);
GLES20.glVertexAttribPointer(mTexturePositionHandle, 2, GLES20.GL_FLOAT, false, 0, 0);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
// 绑定纹理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mExtTextureIds[0]);
GLES20.glUniform1f(mExtTextureHandle, 0);
// 绘制
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
GLES20.glDisableVertexAttribArray(mVertexPositionHandle);
GLES20.glDisableVertexAttribArray(mTexturePositionHandle);
}
public SurfaceTexture getSurfaceTexture() {
return mSurfaceTexture;
}
public void release() {
if (mExtTextureIds != null && mExtTextureIds[0] != 0) {
GLES20.glDeleteTextures(1, mExtTextureIds, 0);
}
if (mFragmentBufferIds != null && mFragmentBufferIds[0] != 0) {
GLES20.glDeleteBuffers(1, mFragmentBufferIds, 0);
}
if (mVertexBufferIds != null && mVertexBufferIds[0] != 0) {
GLES20.glDeleteBuffers(1, mVertexBufferIds, 0);
}
GLES20.glDeleteProgram(mProgram);
}
}
3、创建一个CameraView类,该类直接继承了自定义的GLSurfaceView:
package com.leilu.mycamera.camera;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.util.AttributeSet;
import com.leilu.mycamera.EGLThread;
import com.leilu.mycamera.MyGLSurfaceView;
import static android.opengl.GLES10.glClear;
import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.glClearColor;
import static com.leilu.mycamera.EGLThread.RENDERMODE_WHEN_DIRTY;
/**
* Created by ll on 2018/9/13.
*/
public class CameraView extends MyGLSurfaceView implements EGLThread.OnEGLThreadListener, SurfaceTexture.OnFrameAvailableListener {
private static final int FRONT_CAMERA_ID = 1;
private static final int BACK_CAMERA_ID = 0;
private CameraHelper mCameraHelper;
private CameraRender mCameraRender;
private int mCameraId = BACK_CAMERA_ID;
public CameraView(Context context) {
this(context, null);
}
public CameraView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CameraView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setRenderMode(RENDERMODE_WHEN_DIRTY);
setRenderListener(this);
mCameraHelper = new CameraHelper();
mCameraRender = new CameraRender(context);
}
@Override
public void onCreate() {
mCameraRender.init(this);
}
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
requestRender();
}
@Override
public void onSurfaceChanged(Object surface, int width, int height) {
mCameraRender.setViewPort(width, height);
SurfaceTexture surfaceTexture = mCameraRender.getSurfaceTexture();
if (surfaceTexture != null) {
mCameraHelper.init(mCameraId, width, height, surfaceTexture);
open();
}
}
@Override
public void onDrawFrame() {
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(0, 0, 0, 1);
mCameraRender.draw();
}
public void open() {
mCameraHelper.startPreview();
mCameraRender.setProjectionMatrix(mCameraId == 0);
}
public void close() {
mCameraHelper.stopPreview();
}
@Override
protected void onDetachedFromWindow() {
mCameraHelper.release();
mCameraHelper = null;
mCameraRender.release();
mCameraRender = null;
super.onDetachedFromWindow();
}
public void swithCamera() {
if (mCameraId == BACK_CAMERA_ID) {
mCameraId = FRONT_CAMERA_ID;
} else {
mCameraId = BACK_CAMERA_ID;
}
if (mCameraHelper.switchCamera(mCameraId)) {
mCameraRender.setProjectionMatrix(mCameraId == 0);
}
}
}
上面三个类创建完成以后使用就非常简单了,直接在布局里面嵌入CameraView这个控件即可:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.leilu.mycamera.camera.CameraView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="open"
android:text="打开" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="switchCamera"
android:text="切换" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="close"
android:text="关闭" />
</LinearLayout>
package com.leilu.mycamera;
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.View;
import com.leilu.floatwindow.R;
import com.leilu.mycamera.camera.CameraView;
/**
* Created by ll on 2018/9/13.
*/
public class CameraActivity extends Activity {
private CameraView mCameraView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera);
mCameraView = findViewById(R.id.surface_view);
}
public void open(View view) {
mCameraView.open();
}
public void close(View view) {
mCameraView.close();
}
public void switchCamera(View view) {
mCameraView.swithCamera();
}
}
此时就可以预览摄像头了。
预留的问题:
1、切换摄像头的时候画面会旋转一下,这个问题下次解决
2、在创建自定义GLSurfaceView的时候说到可以实现纹理共享,在这里还没有用到,下篇文章中就使用共享纹理