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

相关推荐
技术liul8 小时前
使用安卓平板,通过USB数据线(而不是Wi-Fi)来控制电脑(版本1)
android·stm32·电脑
_祝你今天愉快10 小时前
Android FrameWork - 开机启动 & Init 进程 初探
android
2501_9160074710 小时前
iOS App 上架实战 从内测到应用商店发布的全周期流程解析
android·ios·小程序·https·uni-app·iphone·webview
TimeFine10 小时前
Android 邮件发送日志
android
杨过过儿10 小时前
【Task02】:四步构建简单rag(第一章3节)
android·java·数据库
百度Geek说10 小时前
播放器视频后处理实践(一)
音视频开发
Wgllss10 小时前
Kotlin 享元设计模式详解 和对象池及在内存优化中的几种案例和应用场景
android·架构·android jetpack
zzywxc78712 小时前
AI 行业应用:金融、医疗、教育、制造业领域的落地案例与技术实现
android·前端·人工智能·chrome·金融·rxjava
sTone8737513 小时前
android studio之外使用NDK编译生成android指定架构的动态库
android·c++
胖虎114 小时前
Android 入门到实战(三):ViewPager及ViewPager2多页面布局
android·viewpager·viewpager2