Android-GLSurfaceView预览摄像头

权限申请

  清单文件中添加权限

1
<uses-permission android:name="android.permission.CAMERA"/>

  申请摄像头权限
1
2
3
ActivityCompat.requestPermissions(this, new String[] {
Manifest.permission.CAMERA
}, 10001);

自定义View

  在项目中,为追求代码简洁,在自定义View中实现GLSurfaceView显示摄像头画面

  创建一个SuperSurfaceView类,使该类继承GLSurfaceView,并重写两个构造方法

1
2
3
4
5
6
7
8
9
public class SuperSurfaceView extends GLSurfaceView {
public SuperSurfaceView(Context context) {
super(context);
}

public SuperSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}

  添加一个设置摄像头类型的方法

1
2
3
4
5
6
private int cameraType = 0; // 摄像头类型,一般情况下,0为后置摄像头,1为前置摄像头

public SuperSurfaceView setCameraType(int _cameraType) {
this.cameraType = _cameraType;
return this;
}

  添加一个开启摄像头预览的方法,后面写完GLSurfaceView渲染器之后再补其中的代码

1
2
3
public void launch() {

}

渲染器

  创建SuperRenderer类,在构造器中接收SuperSurfaceView实例和摄像头的类型,并为摄像头的类型创建一个set方法
  实现GLSurfaceView.Renderer和SurfaceTexture.OnFrameAvailableListener接口,重写如下的四个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class SuperRenderer implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
private int cameraType; // 摄像头类型,0为后置摄像头,1为前置摄像头
private final SuperSurfaceView superSurfaceView;

public SuperRenderer(SuperSurfaceView superSurfaceView, int cameraType) {
this.superSurfaceView = superSurfaceView;
this.cameraType = cameraType;
}

public void setCameraType(int cameraType) {
this.cameraType = cameraType;
}

@Override
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
// GLSurfaceView创建时调用
}

@Override
public void onSurfaceChanged(GL10 gl10, int width, int height) {
// GLSurfaceView大小发生变化时调用
}

@Override
public void onDrawFrame(GL10 gl10) {
// 绘制画面
}

@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
superSurfaceView.requestRender();
}
}

初始化工作

  着色器代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 顶点着色器
private final String vertexShaderCode = "uniform mat4 textureTransform;\n" +
"attribute vec4 position;\n" +
"attribute vec2 inputTextureCoordinate;\n" +
"varying vec2 textureCoordinate;\n" +
"\n" +
"void main() {\n" +
" gl_Position = position;\n" +
" textureCoordinate = inputTextureCoordinate;\n" +
"}";
// 片段着色器
private final String fragmentShaderCode = "#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;\n" +
"uniform samplerExternalOES videoTex;\n" +
"varying vec2 textureCoordinate;\n" +
"\n" +
"void main() {\n" +
" gl_FragColor = texture2D(videoTex, textureCoordinate);\n" +
"}";

  加载着色器

1
2
3
4
5
6
private int loadShader(int shaderType, String shaderSource) {
int shaderHandle = GLES20.glCreateShader(shaderType);
GLES20.glShaderSource(shaderHandle, shaderSource);
GLES20.glCompileShader(shaderHandle);
return shaderHandle;
}

  加载着色器,创建OpenGL ES程序程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private int programId;

private void createProgram() {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
// 创建空的OpenGL ES程序
programId = GLES20.glCreateProgram();
// 添加顶点着色器到程序中
GLES20.glAttachShader(programId, vertexShader);
// 添加片段着色器到程序中
GLES20.glAttachShader(programId, fragmentShader);
// 创建OpenGL ES程序可执行文件
GLES20.glLinkProgram(programId);
// 释放shader资源
GLES20.glDeleteShader(vertexShader);
GLES20.glDeleteShader(fragmentShader);
}

  float数组转换为FloatBuffer

1
2
3
4
5
6
7
8
9
private FloatBuffer floatArr2FloatBuffer(float[] buffer) {
FloatBuffer fb = ByteBuffer.allocateDirect(buffer.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();

fb.put(buffer);
fb.position(0);
return fb;
}

  添加程序到ES环境中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
private int positionHandle;
private int textureHandle;
private int textureTransformHandle;

// 绘制范围矩阵
private final float[] rangeMatrix = {-1, -1, -1, 1, 1, -1, 1, 1};
// 前置摄像头绘制矩阵
private final float[] frontCameraMatrix = {0, 1, 1, 1, 0, 0, 1, 0};
// 后置摄像头绘制矩阵
private final float[] rearCameraMatrix = {1, 1, 0, 1, 1, 0, 0, 0};

/**
* 添加程序到ES环境中
*/
private void activeProgram() {
// 将程序添加到OpenGL ES环境
GLES20.glUseProgram(programId);

// 获取顶点着色器的position句柄
int positionHandle = GLES20.glGetAttribLocation(programId, "position");
// 获取顶点着色器的inputTextureCoordinate句柄
int textureHandle = GLES20.glGetAttribLocation(programId, "inputTextureCoordinate");

textureTransformHandle = GLES20.glGetUniformLocation(programId, "textureTransform");

// 绘制范围矩阵转换后的buffer
FloatBuffer rangeMatrixBuffer = floatArr2FloatBuffer(rangeMatrix);
// 摄像头映像矩阵转换后的buffer
FloatBuffer cameraMatrixBuffer;
if(cameraType == 0){
cameraMatrixBuffer = floatArr2FloatBuffer(rearCameraMatrix);
} else{
cameraMatrixBuffer = floatArr2FloatBuffer(frontCameraMatrix);
}

// 准备positionHandle坐标数据
GLES20.glVertexAttribPointer(positionHandle, 2, GLES20.GL_FLOAT, false, 8, rangeMatrixBuffer);
// 准备textureHandle坐标数据
GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 8, cameraMatrixBuffer);

// 启用positionHandle的句柄
GLES20.glEnableVertexAttribArray(positionHandle);
// 启用textureHandle的句柄
GLES20.glEnableVertexAttribArray(textureHandle);
}

  在onSurfaceCreated()中调用

1
2
3
4
5
6
7
8
9
@Override
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
// 设置背景颜色
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
// 加载着色器,创建OpenGL ES程序程序
createProgram();
// 添加程序到ES环境中
activeProgram();
}

窗口大小改变时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private final float[] orthoMatrix = new float[16];  // 接收正交投影的变换矩阵
private final float[] cameraMatrix = new float[16]; // 接收相机变换矩阵
private final float[] scaleMatrix = new float[16]; // 接收缩放变换矩阵

@Override
public void onSurfaceChanged(GL10 gl10, int width, int height) {
// 设置视窗大小和位置
GLES20.glViewport(0, 0, width, height);

// x y z 代表缩放到比例
Matrix.scaleM(scaleMatrix,0, 1f,1f,1f);

float ratio = (float) width / height;
Matrix.orthoM(orthoMatrix, 0,
-1, 1,
-ratio, ratio,
1, 7);

// 设置观察视角 eye相机坐标 center 目标坐标 up 相机正上方 向量vuv(相机头部指向)
Matrix.setLookAtM(cameraMatrix, 0,
0, 0, 1,
0, 0, 0,
0, 1, 0);

Matrix.multiplyMM(scaleMatrix, 0, orthoMatrix, 0, cameraMatrix, 0);
}

绘制画面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private SurfaceTexture surfaceTexture;  // 用于建立摄像头和GLSurfaceView之间的连接
public boolean isBound = false;

@Override
public void onDrawFrame(GL10 gl10) {
if (isBound) {
activeProgram();
isBound = false;
}

if (surfaceTexture != null) {
// 清除屏幕缓存和深度缓存
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
GLES20.glUniformMatrix4fv(textureTransformHandle, 1, false, scaleMatrix, 0);
// 根据顶点数据绘制平面图形
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, rangeMatrix.length / 2);
surfaceTexture.updateTexImage();
}
}

操作摄像头

  打开摄像头,关闭摄像头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private Camera camera;

public void openCamera() throws Exception {
if (surfaceTexture == null) {
surfaceTexture = new SurfaceTexture(createTextureId());
surfaceTexture.setOnFrameAvailableListener(this);
}

camera = Camera.open(cameraType);
camera.setPreviewTexture(surfaceTexture);
camera.startPreview();
}

public void closeCamera() throws Exception {
if (camera != null) {
camera.setPreviewTexture(null);
camera.stopPreview();
camera.release();
camera = null;
}
}

调用方式

  在之前没写完的launch()方法中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private SuperRenderer superRenderer;

public void launch() throws Exception {
if (superRenderer == null) {
// 创建渲染器对象
superRenderer = new SuperRenderer(this, cameraType);
// 设置OpenGL ES版本
this.setEGLContextClientVersion(2);
// 设置渲染器
this.setRenderer(superRenderer);
//将模式设置为RENDERMODE_WHEN_DIRTY,这样可以减少渲染次数,也就可以减少电量的使用以及更少的使用系统的GPU和CPU资源.
this.setRenderMode(RENDERMODE_WHEN_DIRTY);
} else {
// 关闭摄像头
superRenderer.closeCamera();
// 设置摄像头类型
superRenderer.setCameraType(cameraType);

superRenderer.isBound = true;
}

// 打开摄像头
superRenderer.openCamera();
}

  并且在SuperSurfaceView中添加close()方法供关闭

1
2
3
4
5
public void close() throws Exception {
if (superRenderer != null) {
superRenderer.closeCamera();
}
}

  在Activity中调用时

1
2
3
4
5
6
7
8
9
private void initCamera() {
SuperSurfaceView superFaceView = findViewById(R.id.superFaceView);

try {
superFaceView.setCameraType(cameraType).launch();
} catch (Exception e) {
e.printStackTrace();
}
}

  Activity销毁时

1
2
3
4
5
6
7
8
9
@Override
protected void onDestroy() {
super.onDestroy();
try {
superFaceView.close();
} catch (Exception e) {
e.printStackTrace();
}
}

  点击按钮切换前后摄像头时

1
2
3
4
5
6
7
Button switch_camera = findViewById(R.id.switch_camera);

// 切换前后摄像头
switch_camera.setOnClickListener(view -> {
cameraType = (cameraType == 0) ? 1 : 0;
initCamera();
});

获取当前帧

  获取当前帧的bitmap,通过接口的方式传输bitmap数据,在SuperSurfaceView类中添加如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* 获取bitmap的接口
*/
public interface BitmapCallback{
void onResponse(Bitmap bitmap);
}

/**
* 获取bitmap
*/
public void getBitmap(BitmapCallback bitmapCallback) {
queueEvent(new Runnable() {
@Override
public void run() {
// 获取GLSurfaceView的宽度和高度
int width = SuperSurfaceView.this.getWidth();
int height = SuperSurfaceView.this.getHeight();

// 创建一个Bitmap对象
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

// 创建一个IntBuffer对象
IntBuffer pixelBuffer = IntBuffer.allocate(width * height);

// 读取GLSurfaceView的像素数据
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, pixelBuffer);

// 将IntBuffer中的像素数据复制到Bitmap对象中
pixelBuffer.position(0);
bitmap.copyPixelsFromBuffer(pixelBuffer);

Matrix matrix = new Matrix();
matrix.postScale(1, -1); // 翻转垂直方向
bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);

if (bitmapCallback != null) {
bitmapCallback.onResponse(bitmap);
}
}
});
}

  在Activity中的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
superFaceView.getBitmap(new SuperSurfaceView.BitmapCallback() {
@Override
public void onResponse(Bitmap bitmap) {
// 如果要显示bitmap图像。需在UI线程中
runOnUiThread(new Runnable() {
@Override
public void run() {
// 在UI线程中更新
ImageView imageView = new ImageView(MainActivity.this);
imageView.setImageBitmap(bitmap);
}
});
}
});

页面布局

  在布局文件中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">

<com.wanzheng.supersurfaceview.SuperSurfaceView
android:id="@+id/superFaceView"
android:layout_width="300dp"
android:layout_height="530dp"/>

<Button
android:text="切换摄像头"
android:id="@+id/switch_camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>

  预览

附,开源地址:https://github.com/xxinPro/SuperSurfaceView