[OpenGL]未来视觉2-Android摄像头帧采集

大家好,我系苍王。
以下是我这个系列的相关文章,有兴趣可以参考一下,可以给个喜欢或者关注我的文章。

[Android]如何做一个崩溃率少于千分之三噶应用app--章节列表

Android组件化架构热卖中

这节相机的渲染的介绍,只涉及到二维平面的渲染,所以不需要关注三维变量。
先看一下总图


OpenGL采集总图.png

下面是相机采集初始化处理

JNIEXPORT jint JNICALL
Java_com_cangwang_magic_util_OpenGLJniLib_magicBaseInit(JNIEnv *env, jobject obj,
                                                        jobject surface,jint width,jint height,jobject assetManager) {
    std::unique_lock<std::mutex> lock(gMutex);
    if(glCamera){
        glCamera->stop();
        delete glCamera;
    }
    //创建window
    ANativeWindow *window = ANativeWindow_fromSurface(env,surface);
    //获取文件管理
    AAssetManager *manager = AAssetManager_fromJava(env,assetManager);
    //初始化相机引擎
    glCamera = new CameraEngine(window);
    glCamera->setAssetManager(manager);
    glCamera->resize(width,height);
    //创建相机采集
    return glCamera->create();
}

需要先确定好相机的变换矩阵,这个初始化并不重要。
{ 1,0,0,0,
0,1,0,0,
0,0,1,0,
0,0,0,1}

CameraEngine::CameraEngine(ANativeWindow *window): mWindow(window),mEGLCore(new EGLCore()),
                                                   mAssetManager(nullptr),mTextureId(0),mTextureLoc(0),
                                                   mMatrixLoc(0){
    //清空mMatrix数组
    memset(mMatrix,0, sizeof(mMatrix));
    mMatrix[0] = 1;
    mMatrix[5] = 1;
    mMatrix[10] = 1;
    mMatrix[15] = 1;
}

初始化采集相机纹理的参数。这里需要了解,采集相机是需要GL_TEXTURE_EXTERNAL_OES来绑定相机纹理,因为相机数据是YUV的编码格式,而显示到屏幕上应该是RGB或者RGBA格式,那么就需要转换,如果我们采集后再自己手动转换,将非常耗费GPU和CPU资源,Opengl提供了更加底层的采集解析才能平稳的将数据转化,这里就需要引用到GLES2/gl2ext.h,这是独有的扩展纹理库。

int CameraEngine::create() {
    //这里面需要初始化EGL
    if (!mEGLCore->buildContext(mWindow)){
        return -1;
    }
    //读取顶点着色器
    std::string *vShader = readShaderFromAsset(mAssetManager,"camera.vert");
    //读取片段着色器
    std::string *fShader = readShaderFromAsset(mAssetManager,"camera.frag");
    //加载程序
    mProgram = loadProgram(vShader->c_str(),fShader->c_str());

    //生成纹理贴图
    glGenTextures(1,&mTextureId);
    //绑定纹理,这里面使用GL_TEXTURE_EXTERNAL_OES用于采集相机纹理
    glBindTexture(GL_TEXTURE_EXTERNAL_OES,mTextureId);
    //设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色,少量计算,渲染比较快,但是效果差
    glTexParameterf(GL_TEXTURE_EXTERNAL_OES,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
    //设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色,需要算法计算,用时相对变长,效果好
    glTexParameterf(GL_TEXTURE_EXTERNAL_OES,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    //这里GL_TEXTURE_WRAP_S 纹理坐标是以S轴方向与T轴方向纹理(对应平面坐标x,y方向)
    glTexParameterf(GL_TEXTURE_EXTERNAL_OES,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_EXTERNAL_OES,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);

    //初始化矩阵绑定
    mMatrixLoc = glGetUniformLocation(mProgram,"mMatrix");
    //初始化纹理绑定
    mTextureLoc = glGetUniformLocation(mProgram,"sTexture");
    //使用白色清屏
    glClearColor(1.0f,1.0f,1.0f,1.0f);

    delete vShader;
    delete fShader;
    return mTextureId;
}

EGL是介于诸如OpenGL 或OpenVG的Khronos渲染API与底层本地平台窗口系统的接口。它被用于处理图形管理、表面/缓冲捆绑、渲染同步及支援使用其他Khronos API进行的高效、加速、混合模式2D和3D渲染。这里用于做离屏渲染(缓冲渲染)。介绍一下EGL初始化过程

GLboolean EGLCore::buildContext(ANativeWindow *window) {
    //与本地窗口通信
    mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (mDisplay == EGL_NO_DISPLAY){
        ALOGE("eglGetDisplay failed: %d",eglGetError());
        return GL_FALSE;
    }

    GLint majorVersion;
    GLint minorVersion;
    //获取支持最低和最高版本
    if (!eglInitialize(mDisplay,&majorVersion,&minorVersion)){
        ALOGE("eglInitialize failed: %d",eglGetError());
        return GL_FALSE;
    }

    EGLConfig config;
    EGLint numConfigs = 0;
    //颜色使用565,读写类型需要egl扩展
    EGLint attribList[] = {
            EGL_RED_SIZE,5, //指定RGB中的R大小(bits)
            EGL_GREEN_SIZE,6, //指定G大小
            EGL_BLUE_SIZE,5,  //指定B大小
            EGL_RENDERABLE_TYPE,EGL_OPENGL_ES3_BIT_KHR, //渲染类型,为相机扩展类型
            EGL_SURFACE_TYPE,EGL_WINDOW_BIT,  //绘图类型,
            EGL_NONE
    };

    //让EGL推荐匹配的EGLConfig
    if(!eglChooseConfig(mDisplay,attribList,&config,1,&numConfigs)){
        ALOGE("eglChooseConfig failed: %d",eglGetError());
        return GL_FALSE;
    }

    //找不到匹配的
    if (numConfigs <1){
        ALOGE("eglChooseConfig get config number less than one");
        return GL_FALSE;
    }

    //创建渲染上下文
    //只使用opengles3
    GLint contextAttrib[] = {EGL_CONTEXT_CLIENT_VERSION,3,EGL_NONE};
    // EGL_NO_CONTEXT表示不向其它的context共享资源
    mContext = eglCreateContext(mDisplay,config,EGL_NO_CONTEXT,contextAttrib);
    if (mContext == EGL_NO_CONTEXT){
        ALOGE("eglCreateContext failed: %d",eglGetError());
        return GL_FALSE;
    }

    EGLint format = 0;
    if (!eglGetConfigAttrib(mDisplay,config,EGL_NATIVE_VISUAL_ID,&format)){
        ALOGE("eglGetConfigAttrib failed: %d",eglGetError());
        return GL_FALSE;
    }
    ANativeWindow_setBuffersGeometry(window,0,0,format);

    //创建On-Screen 渲染区域
    mSurface = eglCreateWindowSurface(mDisplay,config,window,0);
    if (mSurface == EGL_NO_SURFACE){
        ALOGE("eglCreateWindowSurface failed: %d",eglGetError());
        return GL_FALSE;
    }

    //把EGLContext和EGLSurface关联起来
    if (!eglMakeCurrent(mDisplay,mSurface,mSurface,mContext)){
        ALOGE("eglMakeCurrent failed: %d",eglGetError());
        return GL_FALSE;
    }

    ALOGD("buildContext Succeed");
    return GL_TRUE;
}

初始化之后,等待相机回调后,才可以开始开启绘制。

 fun initOpenGL(surface: Surface, width: Int, height: Int){
        mExecutor.execute {
            //获取纹理id
            val textureId = OpenGLJniLib.magicBaseInit(surface,width,height,BaseApplication.context.assets)
            if (textureId < 0){  //返回纹理绑定是否正常
                Log.e(TAG, "surfaceCreated init OpenGL ES failed!")
                return@execute
            }
            //需要使用surfaceTexture来做纹理装载
            mSurfaceTexture = SurfaceTexture(textureId)
            //添加纹理变化回调
            mSurfaceTexture?.setOnFrameAvailableListener { 
                    //开始画图
                    drawOpenGL() 
            }
            ……省略代码……
        }
    }

这里需要读取到相机的变换矩阵纹理,矩阵中包括相机图片的图像是是否正对你,是否有角度偏移。我们都可以通过这个矩阵来调整

JNIEXPORT void JNICALL
Java_com_cangwang_magic_util_OpenGLJniLib_magicBaseDraw(JNIEnv *env, jobject obj,jfloatArray matrix_) {
    //获取矩阵,数组转指针
    jfloat *matrix = env->GetFloatArrayElements(matrix_,NULL);

    std::unique_lock<std::mutex> lock(gMutex);
    if (!glCamera){
        ALOGE("draw error, glCamera is null");
        return;
    }
    //启动画图
    glCamera->draw(matrix);
    //释放矩阵内存
    env->ReleaseFloatArrayElements(matrix_,matrix,0);
}

使用GL_TEXTURE_EXTERNAL_OES纹理绘制,matrix是相机采集时传入的矩阵参数。

void CameraEngine::draw(GLfloat *matrix) {
    glViewport(0,0,mWidth,mHeight);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(mProgram);
    //激活纹理
    glActiveTexture(GL_TEXTURE0);
    //绑定纹理
    glBindTexture(GL_TEXTURE_EXTERNAL_OES,mTextureId);
    //加载纹理
    glUniform1i(mTextureLoc,0);
    //加载矩阵
    glUniformMatrix4fv(mMatrixLoc,1,GL_FALSE,matrix);
    //开启顶点数组缓冲区,第0个
    glEnableVertexAttribArray(ATTRIB_POSITION);
    //参数1:顶点数组索引,参数2:每次取的数量 参数3:数据格式 参数4:是否需要浮点转换 参数5:跨距取值,参数6:保存顶点属性数据的缓冲区指针
    glVertexAttribPointer(ATTRIB_POSITION,VERTEX_POS_SIZE,GL_FLOAT,GL_FALSE,0,VERTICES);
    //开启顶点数组缓冲区 第1个
    glEnableVertexAttribArray(ATTRIB_TEXCOORD);
    glVertexAttribPointer(ATTRIB_TEXCOORD,TEX_COORD_POS_SZIE,GL_FLOAT,GL_FALSE,0,TEX_COORDS);
    //画方形
    glDrawArrays(GL_TRIANGLE_STRIP,0,VERTEX_NUM);

    //关闭缓冲区
    glDisableVertexAttribArray(ATTRIB_POSITION);
    //关闭缓冲区
    glDisableVertexAttribArray(ATTRIB_TEXCOORD);

    //清空缓冲区,将指令送往硬件立刻执行
    glFlush();
    //缓冲区交换
    mEGLCore->swapBuffer();
}

说一下离屏渲染之后,交换到前台显示的原理

void EGLCore::swapBuffer() {
    //双缓冲绘图,原来是检测出前台display和后台缓冲的差别的dirty区域,然后再区域替换buffer
    //1)首先计算非dirty区域,然后将非dirty区域数据从上一个buffer拷贝到当前buffer;
    //2)完成buffer内容的填充,然后将previousBuffer指向buffer,同时queue buffer。
    //3)Dequeue一块新的buffer,并等待fence。如果等待超时,就将buffer cancel掉。
    //4)按需重新计算buffer
    //5)Lock buffer,这样就实现page flip,也就是swapbuffer
    eglSwapBuffers(mDisplay,mSurface);
}
glsl分析.png

顶点着色器输入相机转换矩阵,来调整xy方向显示。

#version 300 es

layout(location=0) in vec4 aPosition;
layout(location=1) in vec4 aTexCoord;
//相机转换矩阵
uniform mat4 mMatrix;

out vec2 vTexCoord;

void main() {
   //矩阵调整相机显示,相机像素的坐标
    vTexCoord = (mMatrix * aTexCoord).xy;
    gl_Position = aPosition;
}

这里需要注意的是片段着色器,需要声明使用opengl扩展库,才能使用samperExternalOES提示相机采集数据,并提示片段着色器采集之后转换。

#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require

precision highp float;
//相机采集纹理
uniform samplerExternalOES sTexture;
//坐标
in vec2 vTexCoord;

out vec4 fragColor;

void main() {
    /texture/将与纹理坐标对应的纹理值从内存中取出来,像素坐标和像素纹理关联
    //输出片段着色器的色值
    fragColor = texture(sTexture, vTexCoord);
}

Native底层的相机图像采集介绍就到这,下一节会介绍,相机采帧转换介绍。
例子在(MagicCamera3)[https://github.com/cangwang/MagicCamera3]的CameraActivity当中。

群号是316556016,也可以扫码进群。我在这里期待你们的加入!!!

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

推荐阅读更多精彩内容