OpenGL ES如何渲染Camera1的NV21格式画面

网上关于如何使用OpenGL ES直接渲染NV21格式画面的文章寥寥无几,大多数都是直接用SurfaceTexture提供的纹理来渲染,本文处理的情况是Camera1的回调给了NV21的数组,编写一个滤镜来渲染出来。

理论部分

因为OpenGL要求的是rgb格式,所以需要将yuv转换成rgb格式,转换公式如下:

需要注意的是OpenGL ES的内置矩阵实际是一列一列构建的,比如YUV和RGB的转换矩阵构建是:

c 复制代码
mat3 convertMat = mat3(1.0, 1.0, 1.0,      //第一列
                       0.0,-0.338,1.732, //第二列
                       1.371,-0.698, 0.0);//第三列

在滤镜中需要创建两个纹理,textureY和textureUV,激活纹理时Y的format使用GL_LUMINANCE,UV使用GL_LUMINANCE_ALPHA。

滤镜编写

顶点着色器,常规写法即可。

c 复制代码
#version 300 es
layout(location = 0) in vec4 a_Position;
layout(location = 1) in vec2 aTexture;
out vec2 vTexture;
void main()
{
    gl_Position = a_Position;
    vTexture = aTexture;
}

片元着色器,主要做yuv到rgb的转换。

c 复制代码
#version 300 es
precision mediump float;
out vec4 FragColor;
in vec2 vTexture;
uniform sampler2D textureY;
uniform sampler2D textureUV;
void main() {
    //yuv转化得到的rgb向量数据
    vec3 rgb;
    vec3 yuv;
    //分别取yuv各个分量的采样纹理
    yuv.x = texture(textureY, vTexture).r;
    yuv.y = texture(textureUV, vTexture).a - 0.5;
    yuv.z = texture(textureUV, vTexture).r - 0.5;
    //yuv转化为rgb
    rgb = mat3(1.0, 1.0, 1.0,
                0.0, -0.338, 1.732,
                1.4075, -0.7169, 0.0)*yuv;
    FragColor = vec4(rgb, 1.0);
}

滤镜类编写:

kotlin 复制代码
class YuvOESFilter(context: Context): GLImageFilter(context) {
    // 预览的宽高, camera一般宽高反过来,如 720*1280
    private var mVideoWidth: Int = -1
    private var mVideoHeight: Int = -1

    // 纹理接收者
    private var mTextureYHandler: Int = -1
    private var mTextureUVHandler: Int = -1
    // 两个纹理
    private val textures = IntArray(2)
    // 两个buffer
    private var bufferY: ByteBuffer? = null
    private var bufferUV: ByteBuffer? = null
}

在渲染之前必须要确定画面的宽高,为了确定buffer的大小。

kotlin 复制代码
fun setVideoSize(videoW: Int, videoH: Int) {
    mVideoWidth = videoW
    mVideoHeight = videoH
}

每一帧回调时,设置buffer的数据。

kotlin 复制代码
fun setNV21Data(data: ByteArray) {

    try {
        // 初始化
        if (bufferY == null) {
            bufferY = ByteBuffer.allocate(mVideoWidth * mVideoHeight)
            bufferY!!.order(ByteOrder.nativeOrder())
        }

        if (bufferUV == null) {
            bufferUV = ByteBuffer.allocate(mVideoWidth * mVideoHeight / 2)
            bufferUV!!.order(ByteOrder.nativeOrder())
        }
        // Y的数据是宽*高
        bufferY!!.put(data, 0, mVideoWidth * mVideoHeight)
        bufferY!!.position(0)
        // UV的数据是Y的一半
        bufferUV!!.put(data, mVideoWidth * mVideoHeight, mVideoWidth * mVideoHeight / 2)
        bufferUV!!.position(0)
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

接下来是渲染,设置顶点坐标、纹理坐标部分省略了,重点在于两个纹理的激活与绑定。

kotlin 复制代码
override fun onDrawTexture(
    textureId: Int,
    vertexBuffer: FloatBuffer?,
    textureBuffer: FloatBuffer?
) {
    ...
    if (textures[0] == 0) {
        // 普通的创建纹理逻辑
        setTexture()
    }
    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
    //激活指定纹理单元
    GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
    //绑定纹理ID到纹理单元
    //y
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0])
    GLES30.glUniform1i(mTextureYHandler, 0)
    GLES30.glTexImage2D(
        GLES30.GL_TEXTURE_2D,
        0,
        GLES30.GL_LUMINANCE,
        mVideoHeight,
        mVideoWidth,
        0,
        GLES30.GL_LUMINANCE,
        GLES30.GL_UNSIGNED_BYTE,
        bufferY
    )
    GLES30.glActiveTexture(GLES30.GL_TEXTURE1)
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[1])
    GLES30.glUniform1i(mTextureUVHandler, 1)
    // 注意使用GLES30.GL_LUMINANCE_ALPHA
    GLES30.glTexImage2D(
        GLES30.GL_TEXTURE_2D,
        0,
        GLES30.GL_LUMINANCE_ALPHA,
        mVideoHeight / 2,
        mVideoWidth / 2,
        0,
        GLES30.GL_LUMINANCE_ALPHA,
        GLES30.GL_UNSIGNED_BYTE,
        bufferUV
    )

    GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mVertexCount)
    // 解绑
    GLES30.glDisableVertexAttribArray(mPositionHandle)
    GLES30.glDisableVertexAttribArray(mTextureCoordinateHandle)
    GLES30.glBindTexture(textureType, 0)
}

override fun release() {
    super.release()
    // GLES20.glDeleteProgram(mProgramHandle);
    bufferY?.clear()
    bufferUV?.clear()
}

这样就完成了渲染NV21的核心逻辑,每帧回调时触发一次滤镜的draw即可实现预览。

参考

在面试中,被反复提及的 OpenGL NV21 图像渲染

相关推荐
网络研究院2 小时前
Android 安卓内存安全漏洞数量大幅下降的原因
android·安全·编程·安卓·内存·漏洞·技术
凉亭下2 小时前
android navigation 用法详细使用
android
小比卡丘5 小时前
C语言进阶版第17课—自定义类型:联合和枚举
android·java·c语言
前行的小黑炭5 小时前
一篇搞定Android 实现扫码支付:如何对接海外的第三方支付;项目中的真实经验分享;如何高效对接,高效开发
android
落落落sss7 小时前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
代码敲上天.7 小时前
数据库语句优化
android·数据库·adb
GEEKVIP10 小时前
手机使用技巧:8 个 Android 锁屏移除工具 [解锁 Android]
android·macos·ios·智能手机·电脑·手机·iphone
model200511 小时前
android + tflite 分类APP开发-2
android·分类·tflite
彭于晏68912 小时前
Android广播
android·java·开发语言
与衫13 小时前
掌握嵌套子查询:复杂 SQL 中 * 列的准确表列关系
android·javascript·sql