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

相关推荐
CYRUS STUDIO2 小时前
FART 主动调用组件设计和源码分析
android·逆向·源码分析·fart·脱壳
zimoyin7 小时前
kotlin Android AccessibilityService 无障碍入门
android·开发语言·kotlin
韩仔搭建18 小时前
第二章:安卓端启动流程详解与疑难杂症调试手册
android·ui·娱乐
A-花开堪折18 小时前
Android7 Input(七)App与input系统服务建立连接
android
冰糖葫芦三剑客18 小时前
Android 自定义悬浮拖动吸附按钮
android
吃汉堡吃到饱18 小时前
【Android】从Choreographer到UI渲染(二)
android·ui
微信公众号:AI创造财富18 小时前
显示的图标跟UI界面对应不上。
android·ui
aningxiaoxixi18 小时前
安卓 Audio Stream 类型
android
奔跑吧 android19 小时前
【android bluetooth 协议分析 01】【HCI 层介绍 3】【NUMBER_OF_COMPLETED_PACKETS 事件介绍】
android·bluetooth·hci·bt·gd·aosp13
_龙小鱼_21 小时前
Kotlin扩展简化Android动画开发
android·开发语言·kotlin