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 图像渲染

相关推荐
studyForMokey1 小时前
【Android面试】View绘制流程专题
android·面试·职场和发展
jjinl3 小时前
Android 资源说明
android
恋猫de小郭5 小时前
Swift 6.3 正式发布支持 Android ,它能在跨平台发挥什么优势?
android·前端·flutter
一只会跑会跳会发疯的猴子5 小时前
php操作ssl,亲测可用
android·php·ssl
程序员码歌6 小时前
火爆了,一个Skill搞定AI热点自动化:RSS 聚合 + AI 筛选 + 公众号 + 邮件全流程
android·前端·ai编程
优选资源分享6 小时前
小白转文字 v1.2.8.0 | 安卓离线免费音视频转写工具
android·音视频
安卓机器6 小时前
安卓玩机自做小工具------用于ROM修改 安卓设备联机应用扫描工具 查看应用中文名称 包名 应用路径等
android·修改rom·定制rom·修改系统应用
梦里花开知多少6 小时前
深入理解Android binder线程模型
android·架构
千里马学框架6 小时前
aospc/c++的native 模块VScode和Clion
android·开发语言·c++·vscode·安卓framework开发·clion·车载开发
洞见不一样的自己7 小时前
深度解析Kotlin泛型:从基础到实战
android