相机使用的是Camerax
添加Camerax依赖
implementation "androidx.camera:camera-core:1.0.0-alpha05"
implementation "androidx.camera:camera-camera2:1.0.0-alpha05"
目录结构为
图片.png
vertex_shader.glsl顶点程序内容为:
//有4个顶点,会被GPU同时执行4次
attribute vec4 vPosition;//顶点坐标 默认gl_Position是4个值,但从外面传来的只有X,Y,后面2个 会被设置为0
attribute vec2 fPosition;//1个顶点坐标,有2个值 X,Y
varying vec2 textureCoordinate;//传给纹理shader
void main(){
textureCoordinate = fPosition;
gl_Position = vPosition;
}
fragment_shader.glsl纹理程序内容为:
// GLES20.glViewport(0,0,width,height)这个函数设置了画布大小 , 这个纹理程序会被同时调用width *height 次
// 着色器纹理扩展类型
#extension GL_OES_EGL_image_external : require
// 设置精度,中等精度
precision mediump float;
//这里的值代表纹理里的每个像素点坐标
varying vec2 textureCoordinate;//varying插值变量类型,从顶点程序传过来的但会经过光栅化插值计算变得不一样。
uniform samplerExternalOES sTexture;
void main(){
gl_FragColor = texture2D(sTexture,textureCoordinate);
}
创建opengles工具类ViUtils.kt。都是通用固定的代码
package com.nav.vio
import android.content.Context
import android.opengl.GLES20
import com.blankj.utilcode.util.ResourceUtils
import java.io.BufferedReader
import java.io.InputStream
import java.io.InputStreamReader
import java.lang.Exception
class ViUtils {
companion object {
fun loadShader(type: Int, shaderCode: String): Int {
//创建程序
var share = GLES20.glCreateShader(type)
//加载程序文件
GLES20.glShaderSource(share, shaderCode)
//编译程序
GLES20.glCompileShader(share)
//判断程序是否编译成功
var status = intArrayOf(1)
GLES20.glGetShaderiv(share, GLES20.GL_COMPILE_STATUS, status, 0);
if (status[0] != GLES20.GL_TRUE) {
GLES20.glDeleteShader(share)
return 0
}
return share
}
//获取raw文件为字符串
fun getGLResource(context: Context, rawId: Int):String{
var inputStream = context.resources.openRawResource(rawId)
var reader = BufferedReader(InputStreamReader(inputStream))
var sb = StringBuffer()
var line:String?
try {
while ((reader.readLine().also {iva-> line = iva }) != null){
sb.append(line).append("\n")
}
}catch (e: Exception){
e.printStackTrace()
}
return sb.toString()
}
//创建opengl项目
fun createProgram(vertexSource:String, fragmentSource:String):Int{
//获取shader
var vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource)//顶点程序
var fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource)//纹理程序
if(vertexShader != 0 && fragmentShader != 0){
//创建项目
var program = GLES20.glCreateProgram().also {
GLES20.glAttachShader(it, vertexShader)//添加顶点程序到program
GLES20.glAttachShader(it,fragmentShader)//添加纹理程序到program
GLES20.glLinkProgram(it)
}
var status = intArrayOf(1)
GLES20.glGetProgramiv(program,GLES20.GL_LINK_STATUS,status,0)
if(status[0] != GLES20.GL_TRUE){
GLES20.glDeleteProgram(program)
return 0
}
return program
}else{
return 0
}
}
}
}
创建ViCameraView.java 文件
package com.nav.vio;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Surface;
import androidx.camera.core.CameraX;
import androidx.camera.core.Preview;
import androidx.camera.core.PreviewConfig;
import androidx.lifecycle.LifecycleOwner;
import com.blankj.utilcode.util.LogUtils;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class ViCameraView extends GLSurfaceView implements GLSurfaceView.Renderer {
private SurfaceTexture surfaceTexture;
private ViCameraDrawer cameraDrawer;
private int texture = 0;//这个纹理ID可以随意指定,比如texture =101等
public ViCameraView(Context context) {
super(context, null);
}
public ViCameraView(Context context, AttributeSet attrs) {
super(context, attrs);
setEGLContextClientVersion(2);//设置opengl版本2
initCameraX();
}
private void initCameraX() {
PreviewConfig config = new PreviewConfig.Builder()
.setLensFacing(CameraX.LensFacing.BACK)//后置摄像头
.build();
Preview preview = new Preview(config);
CameraX.bindToLifecycle((LifecycleOwner) getContext(),preview);
preview.setOnPreviewOutputUpdateListener(new Preview.OnPreviewOutputUpdateListener() {
@Override
public void onUpdated(Preview.PreviewOutput output) {
surfaceTexture = output.getSurfaceTexture();
setRenderer(ViCameraView.this);
//渲染方式: 手动 RENDERMODE_WHEN_DIRTY
// 自动 RENDERMODE_CONTINUOUSLY
setRenderMode(RENDERMODE_WHEN_DIRTY);//手动调用绘制 onDrawFrame
}
});
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//必须在egl线程里面才能使用下面这些方法
surfaceTexture.attachToGLContext(texture);//创建一个纹理并将纹理附加到当前线程,必须在当前GLSurfaceView的EGL线程里调用
cameraDrawer = new ViCameraDrawer(getContext());
surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
//摄像头回调
//触发 onDrawFrame
requestRender();
}
});
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0,0,width,height);
}
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClearColor(0.0f,0.0f,0.0f,0.0f);
//清空当前缓存
/* GL_COLOR_BUFFER_BIT: 当前可写的颜色缓冲
GL_DEPTH_BUFFER_BIT: 深度缓冲
GL_ACCUM_BUFFER_BIT: 累积缓冲
GL_STENCIL_BUFFER_BIT: 模板缓冲*/
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);//表示清除颜色设为黑色
//将相机的图像更新到纹理图像中,并且会绑定到当前激活的纹理通道,当前默认被激活的纹理通道是0
// GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,texture);
surfaceTexture.updateTexImage();
cameraDrawer.draw(texture);
}
}
创建ViCameraDrawer.kt文件
package com.nav.vio
import android.content.Context
import android.graphics.SurfaceTexture
import android.opengl.GLES10Ext
import android.opengl.GLES11Ext
import android.opengl.GLES20
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import javax.microedition.khronos.opengles.GL
class ViCameraDrawer constructor(context: Context) {
private var mProgram = 0
private var mContext: Context = context
//顶点坐标原点在中心为0,0
private var mVertexCoordinate = floatArrayOf(
-1f, -1f,//左下
1f, -1f,//右下
-1f, 1f,//左上
1f, 1f//右上
)
//创建native内存
private val mVertexBuffer: FloatBuffer =
ByteBuffer.allocateDirect(mVertexCoordinate.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(mVertexCoordinate)
.apply {
this.position(0)
}
//纹理坐标左上角为坐标原点, 纹理坐标位置和顶点坐标位置一一对应
private var mFragmentCoordinate = floatArrayOf(
0f, 1f,//左下 ,
1f, 1f,//右下
0f, 0f,//左上
1f, 0f//右上
)
private val mFragmentBuffer: FloatBuffer =
ByteBuffer.allocateDirect(mFragmentCoordinate.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(mFragmentCoordinate)
.apply {
this.position(0)
}
//glsl顶点坐标引用
private var vPosition = 0
//glsl纹理坐标引用
private var fPosition = 0
//glsl纹理引用
private var sTexture = 0
init {
//创建GLSL程序
var vertexSource = ViUtils.getGLResource(this.mContext, R.raw.vertex_shader)
var fragmentSource = ViUtils.getGLResource(this.mContext, R.raw.fragment_shader)
mProgram = ViUtils.createProgram(vertexSource, fragmentSource)
vPosition = GLES20.glGetAttribLocation(mProgram, "vPosition")
fPosition = GLES20.glGetAttribLocation(mProgram, "fPosition")
sTexture = GLES20.glGetUniformLocation(mProgram, "sTexture")
}
fun draw(textureId: Int) {
//使用mProgram工程
GLES20.glUseProgram(mProgram)
//从native里赋值顶点坐标赋值到glsl程序里,固定写法
GLES20.glEnableVertexAttribArray(vPosition)
GLES20.glVertexAttribPointer(vPosition,
2, //每个坐标点有2个元素
GLES20.GL_FLOAT,//每个坐标点元素是float类型
false,//是否将坐标归一到0,1。这里不需要,就原样按照传入的值
8,//步长,8个元素,
mVertexBuffer)
GLES20.glEnableVertexAttribArray(fPosition)
GLES20.glVertexAttribPointer(fPosition, 2, GLES20.GL_FLOAT, false, 8, mFragmentBuffer)
//
//默认就是激活通道0的所以这行代码可写不写
// GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
// //将纹理对象textureId绑定到当前激活的纹理通道 当前通道为0,因为 updateTexImage里已经绑定了所以不用再绑定.
// GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId)
GLES20.glUniform1i(sTexture, 0)// 赋值为纹理通道0,默认程序的纹理赋值为通道0,所以这行代码可写可不写
//如果还有其他纹理
// GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
// GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureId1)
// GLES20.glUniform1i(sTexture,1)// 赋值为纹理通道1
//按照 三角形 方式绘制, 会按照这种方式绘制,第一个三角形(point0,point1,point2 ),第二个三角形(point1,point2,point3)
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4)//
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,0)//绑定为一个不存在的纹理ID,解绑
}
}
注意记得申请相机权限
最后在自己的布局文件里 加上ViCameraView这个自定义view
运行APP会发现预览的画面是逆时针90度。因为安卓手机摄像头的上方向是屏幕的右边。
图片.png
下篇文章解决这个问题