Android使用opengles實(shí)現(xiàn)相機(jī)預(yù)覽

相機(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è)問題

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容