Android使用opengles实现相机预览

相机使用的是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

下篇文章解决这个问题

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,576评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,515评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,017评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,626评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,625评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,255评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,825评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,729评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,271评论 1 320
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,363评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,498评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,183评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,867评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,338评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,458评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,906评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,507评论 2 359

推荐阅读更多精彩内容