相機(jī)使用的是Camerax
添加Camerax依賴
implementation "androidx.camera:camera-core:1.0.0-alpha05"
implementation "androidx.camera:camera-camera2:1.0.0-alpha05"
目錄結(jié)構(gòu)為

圖片.png
vertex_shader.glsl頂點(diǎn)程序內(nèi)容為:
//有4個(gè)頂點(diǎn),會(huì)被GPU同時(shí)執(zhí)行4次
attribute vec4 vPosition;//頂點(diǎn)坐標(biāo) 默認(rèn)gl_Position是4個(gè)值,但從外面?zhèn)鱽淼闹挥蠿,Y,后面2個(gè) 會(huì)被設(shè)置為0
attribute vec2 fPosition;//1個(gè)頂點(diǎn)坐標(biāo),有2個(gè)值 X,Y
varying vec2 textureCoordinate;//傳給紋理shader
void main(){
textureCoordinate = fPosition;
gl_Position = vPosition;
}
fragment_shader.glsl紋理程序內(nèi)容為:
// GLES20.glViewport(0,0,width,height)這個(gè)函數(shù)設(shè)置了畫布大小 , 這個(gè)紋理程序會(huì)被同時(shí)調(diào)用width *height 次
// 著色器紋理擴(kuò)展類型
#extension GL_OES_EGL_image_external : require
// 設(shè)置精度,中等精度
precision mediump float;
//這里的值代表紋理里的每個(gè)像素點(diǎn)坐標(biāo)
varying vec2 textureCoordinate;//varying插值變量類型,從頂點(diǎn)程序傳過來的但會(huì)經(jīng)過光柵化插值計(jì)算變得不一樣。
uniform samplerExternalOES sTexture;
void main(){
gl_FragColor = texture2D(sTexture,textureCoordinate);
}
創(chuàng)建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 {
//創(chuàng)建程序
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()
}
//創(chuàng)建opengl項(xiàng)目
fun createProgram(vertexSource:String, fragmentSource:String):Int{
//獲取shader
var vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource)//頂點(diǎn)程序
var fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource)//紋理程序
if(vertexShader != 0 && fragmentShader != 0){
//創(chuàng)建項(xiàng)目
var program = GLES20.glCreateProgram().also {
GLES20.glAttachShader(it, vertexShader)//添加頂點(diǎn)程序到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
}
}
}
}
創(chuàng)建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;//這個(gè)紋理ID可以隨意指定,比如texture =101等
public ViCameraView(Context context) {
super(context, null);
}
public ViCameraView(Context context, AttributeSet attrs) {
super(context, attrs);
setEGLContextClientVersion(2);//設(shè)置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);
//渲染方式: 手動(dòng) RENDERMODE_WHEN_DIRTY
// 自動(dòng) RENDERMODE_CONTINUOUSLY
setRenderMode(RENDERMODE_WHEN_DIRTY);//手動(dòng)調(diào)用繪制 onDrawFrame
}
});
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//必須在egl線程里面才能使用下面這些方法
surfaceTexture.attachToGLContext(texture);//創(chuàng)建一個(gè)紋理并將紋理附加到當(dāng)前線程,必須在當(dāng)前GLSurfaceView的EGL線程里調(diào)用
cameraDrawer = new ViCameraDrawer(getContext());
surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
//攝像頭回調(diào)
//觸發(fā) 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);
//清空當(dāng)前緩存
/* GL_COLOR_BUFFER_BIT: 當(dāng)前可寫的顏色緩沖
GL_DEPTH_BUFFER_BIT: 深度緩沖
GL_ACCUM_BUFFER_BIT: 累積緩沖
GL_STENCIL_BUFFER_BIT: 模板緩沖*/
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);//表示清除顏色設(shè)為黑色
//將相機(jī)的圖像更新到紋理圖像中,并且會(huì)綁定到當(dāng)前激活的紋理通道,當(dāng)前默認(rèn)被激活的紋理通道是0
// GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,texture);
surfaceTexture.updateTexImage();
cameraDrawer.draw(texture);
}
}
創(chuàng)建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
//頂點(diǎn)坐標(biāo)原點(diǎn)在中心為0,0
private var mVertexCoordinate = floatArrayOf(
-1f, -1f,//左下
1f, -1f,//右下
-1f, 1f,//左上
1f, 1f//右上
)
//創(chuàng)建native內(nèi)存
private val mVertexBuffer: FloatBuffer =
ByteBuffer.allocateDirect(mVertexCoordinate.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(mVertexCoordinate)
.apply {
this.position(0)
}
//紋理坐標(biāo)左上角為坐標(biāo)原點(diǎn), 紋理坐標(biāo)位置和頂點(diǎn)坐標(biāo)位置一一對(duì)應(yīng)
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頂點(diǎn)坐標(biāo)引用
private var vPosition = 0
//glsl紋理坐標(biāo)引用
private var fPosition = 0
//glsl紋理引用
private var sTexture = 0
init {
//創(chuàng)建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里賦值頂點(diǎn)坐標(biāo)賦值到glsl程序里,固定寫法
GLES20.glEnableVertexAttribArray(vPosition)
GLES20.glVertexAttribPointer(vPosition,
2, //每個(gè)坐標(biāo)點(diǎn)有2個(gè)元素
GLES20.GL_FLOAT,//每個(gè)坐標(biāo)點(diǎn)元素是float類型
false,//是否將坐標(biāo)歸一到0,1。這里不需要,就原樣按照傳入的值
8,//步長(zhǎng),8個(gè)元素,
mVertexBuffer)
GLES20.glEnableVertexAttribArray(fPosition)
GLES20.glVertexAttribPointer(fPosition, 2, GLES20.GL_FLOAT, false, 8, mFragmentBuffer)
//
//默認(rèn)就是激活通道0的所以這行代碼可寫不寫
// GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
// //將紋理對(duì)象textureId綁定到當(dāng)前激活的紋理通道 當(dāng)前通道為0,因?yàn)?updateTexImage里已經(jīng)綁定了所以不用再綁定.
// GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId)
GLES20.glUniform1i(sTexture, 0)// 賦值為紋理通道0,默認(rèn)程序的紋理賦值為通道0,所以這行代碼可寫可不寫
//如果還有其他紋理
// GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
// GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureId1)
// GLES20.glUniform1i(sTexture,1)// 賦值為紋理通道1
//按照 三角形 方式繪制, 會(huì)按照這種方式繪制,第一個(gè)三角形(point0,point1,point2 ),第二個(gè)三角形(point1,point2,point3)
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4)//
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,0)//綁定為一個(gè)不存在的紋理ID,解綁
}
}
注意記得申請(qǐng)相機(jī)權(quán)限
最后在自己的布局文件里 加上ViCameraView這個(gè)自定義view
運(yùn)行APP會(huì)發(fā)現(xiàn)預(yù)覽的畫面是逆時(shí)針90度。因?yàn)榘沧渴謾C(jī)攝像頭的上方向是屏幕的右邊。

圖片.png
下篇文章解決這個(gè)問題