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

简介: 这节相机的渲染的介绍,只涉及到二维平面的渲染,所以不需要关注三维变量。先看一下总图 OpenGL采集总图.png下面是相机采集初始化处理JNIEXPORT jint JNICALLJava_com...

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

 

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底层的相机图像采集介绍就到这,下一节会介绍,相机采帧转换介绍。


 

相关文章
|
2月前
|
安全 API 开发工具
Android平台RTMP推送|轻量级RTSP服务如何实现麦克风|扬声器声音采集切换
Android平台扬声器播放声音的采集,在无纸化同屏等场景下,意义很大,早期低版本的Android设备,是没法直接采集扬声器audio的(从Android 10开始支持),所以,如果需要采集扬声器audio,需要先做系统版本判断,添加相应的权限。
|
2月前
|
编解码 开发工具 Android开发
Android平台实现屏幕录制(屏幕投影)|音频播放采集|麦克风采集并推送RTMP或轻量级RTSP服务
Android平台屏幕采集、音频播放声音采集、麦克风采集编码打包推送到RTMP和轻量级RTSP服务的相关技术实现,做成高稳定低延迟的同屏系统,还需要有配套好的RTMP、RTSP直播播放器
|
3月前
|
编解码 网络协议 前端开发
如何实现Android平台GB28181设备接入模块按需打开摄像头并回传数据
后台采集摄像头,如果想再进一步扩展,可以把android平台gb28181的camera2 demo,都移植过来,实现功能更强大的国标设备侧,这里主要是展示,收到国标平台侧的回传请求后,才打开摄像头,才开始编码打包,最大限度的减少资源的占用
|
3月前
|
编解码 网络协议 Android开发
Android平台GB28181设备接入模块实现后台service按需回传摄像头数据到国标平台侧
我们在做Android平台GB28181设备对接模块的时候,遇到这样的技术需求,开发者希望能以后台服务的形式运行程序,国标平台侧没有视频回传请求的时候,仅保持信令链接,有发起视频回传请求或语音广播时,打开摄像头,并实时回传音视频数据或接收处理国标平台侧发过来的语音广播数据。
|
3月前
|
传感器 API Android开发
Android摄像头采集选Camera1还是Camera2?
Camera1与Camera2是Android平台上的两种摄像头API。Camera1(API1)在Android 5.0后被标记为过时,新项目应优先选用Camera2(API2)。Camera2提供了更精细的控制选项,如曝光时间、ISO感光度等;支持多摄像头管理;采用异步操作提高应用响应速度;并支持RAW图像捕获及实时图像处理。此外,它还具备更好的适配性和扩展性,适用于各类应用场景,如相机应用开发、视频通话和计算机视觉等。因此,在现代Android开发中推荐使用Camera2。
|
5月前
|
Android开发 开发者
Android UI设计中,Theme定义了Activity的视觉风格,包括颜色、字体、窗口样式等,定义在`styles.xml`。
【6月更文挑战第26天】Android UI设计中,Theme定义了Activity的视觉风格,包括颜色、字体、窗口样式等,定义在`styles.xml`。要更改主题,首先在该文件中创建新主题,如`MyAppTheme`,覆盖所需属性。然后,在`AndroidManifest.xml`中应用主题至应用或特定Activity。运行时切换主题可通过重新设置并重启Activity实现,或使用`setTheme`和`recreate()`方法。这允许开发者定制界面并与品牌指南匹配,或提供多主题选项。
85 6
|
5月前
|
Android开发 开发者
Android UI中的Theme定义了Activity的视觉风格,包括颜色、字体、窗口样式等。要更改主题
【6月更文挑战第25天】Android UI中的Theme定义了Activity的视觉风格,包括颜色、字体、窗口样式等。要更改主题,首先在`styles.xml`中定义新主题,如`MyAppTheme`,然后在`AndroidManifest.xml`中设置`android:theme`。可应用于全局或特定Activity。运行时切换主题需重置Activity,如通过`setTheme()`和`recreate()`方法。这允许开发者定制界面以匹配品牌或用户偏好。
56 2
|
5月前
|
Java API Android开发
安卓开发app 调用usb 摄像头 需要用到哪个库
在安卓开发中,调用USB摄像头常常使用libuvc库,这是一个跨平台处理USB视频设备的库。有多个基于libuvc的开源项目简化了在安卓上的使用,如UVCCamera和Android EasyCap UVC。例如,UVCCamera提供了一个更简单的接口来访问USB摄像头,并且可以在Jetpack Compose中显示预览。开发者可以参考官方文档、开源项目以及相关教程和资源来学习和实现这一功能。
|
6月前
|
Java Android开发
Android开发之使用OpenGL实现翻书动画
本文讲述了如何使用OpenGL实现更平滑、逼真的电子书翻页动画,以解决传统贝塞尔曲线方法存在的卡顿和阴影问题。作者分享了一个改造后的外国代码示例,提供了从前往后和从后往前的翻页效果动图。文章附带了`GlTurnActivity`的Java代码片段,展示如何加载和显示书籍图片。完整工程代码可在作者的GitHub找到:https://github.com/aqi00/note/tree/master/ExmOpenGL。
156 1
Android开发之使用OpenGL实现翻书动画
|
6月前
|
Android开发 开发者
Android开发之OpenGL的画笔工具GL10
这篇文章简述了OpenGL通过GL10进行三维图形绘制,强调颜色取值范围为0.0到1.0,背景和画笔颜色设置方法;介绍了三维坐标系及与之相关的旋转、平移和缩放操作;最后探讨了坐标矩阵变换,包括设置绘图区域、调整镜头参数和改变观测方位。示例代码展示了如何使用这些方法创建简单的三维立方体。
78 1
Android开发之OpenGL的画笔工具GL10
下一篇
无影云桌面