当前位置:   article > 正文

OpenGL ES 送显 YUV NV12_opengl nv12

opengl nv12

先来了解 YUV NV12 组成,再来实现 OpenGL ES 送显 YUV NV12。

一、YUV NV12

YUV 是编译 true-color 颜色空间(color space)的种类,Y’UV、YUV、 YCbCr、YPbPr 等专有名词都可以称为 YUV,彼此有重叠。Y 表示明亮度(Luminance 或 Luma),也就是灰阶值,U 和 V 表示的则是色度(Chrominance 或 Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。

YUV 格式分成两种:

紧缩格式(packedformats):将 Y、U、V 值存储 成 MacroPixels 数组,和 RGB 的存放方式类似;

平面格式(planarformats):将 Y、U、V 的三个分量分别存放在不同的矩阵中。

紧缩格式中的 YUV 是混合在一起的,对于 YUV 常见格式有 AYUV 格式(4:4:4 采样、打包格式);YUY2、UYVY(采样、打包格式),有UYVY、YUYV等。平面格式(planarformats)是指每 Y 分量,U 分量和 V 分量都是以独立的平面组织的,也就是说所有的 U 分量必须在 Y 分量后面,而 V 分量在所有的 U 分量后面,此一格式适用于采样(subsample)。平面格式(planarformat)有I420(4:2:0)、YV12、IYUV等。

现在重点来看 YV12 和 NV12。
YV12

YV12 中 Y、U、V 三个平面是分开的。而 NV12 中 Y 是一个平面,UV 共同组成另一个平面。
NV12
不难看出 NV12 中一组 UV 共用四个 Y,也就是上图中以相同颜色标识部分。

二、送显 YUV NV12

OpenGL ES 送显图像到 RGB 屏幕,需要将 NV12 中代表的每个像素转换为 RGB。用到了以下 YUV 到 RGB 的转换公式:

            yuv.r = texture2D(yTexture, vTexCoord).r;
            yuv.g = texture2D(uvTexture, vTexCoord).r - 0.5;
            yuv.b = texture2D(uvTexture, vTexCoord).a - 0.5;
            rgb = mat3(1.0, 1.0, 1.0,
                       0.0, 0.39465, 2.03211,
                       1.13983, -0.5806, 0.0) * yuv;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

yuv.r 就是从纹理中拿到的 Y 分量,yuv.g 相应的是从纹理中拿到的 U 分量,yuv.b 就是 V 分量。

mat3 3x3 矩阵中的系数就是 YUV 转 RGB 公式内的转换系数。

OpenGL ES 送显 NV12 完整流程如下:

  1. 加载 vertex/fragment shaders;
  2. 创建 program object;
  3. 附着 shader object 到 program object;
  4. 绑定 vPosition 到 attribute 0;
  5. 链接 program object;
  6. 检查程序链接状态;
  7. 释放不在使用的 shader 资源;
  8. 存储 program object;
  9. 生成纹理 Y 和 UV;
  10. 激活渲染程序;
  11. 获取 shader 中的顶点变量;
  12. 传递顶点;
  13. 设置纹理层;
  14. 激活纹理;
  15. 绑定纹理;
  16. 设置纹理格式和大小;
  17. 三维绘制;
  18. 窗口显示。

具体代码如下:

#ifndef DISPLAYHANDLER_H
#define DISPLAYHANDLER_H

#include <android/native_window_jni.h>
#include <EGL/egl.h>
#include <GLES2/gl2.h>
#include <malloc.h>
#include "logger.h"

#define TEXTURE_NUM 2

class DisplayHandler {
public:
    DisplayHandler();
    bool initEGL(ANativeWindow *nwin);
    void deinitEGL();
    GLint createProgram();

    void setVideoWH(int width, int height);

    int getVideoWidth() const;
    int getVideoHeight() const;

    void update(unsigned char *yuvBuf);

private:
    GLint initShader(const char *code, GLint type);
    GLuint loadProgram(const char *vShaderStr, const char *fShaderStr);

    EGLDisplay eglDisplay;
    EGLSurface eglSurface;
    EGLContext eglContext;

    int videoWidth;
    int videoHeight;

    GLuint mTextureID[TEXTURE_NUM];
    GLuint glProgram;
};


#endif //DISPLAYHANDLER_H
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

以下是具体 DisplayHandler 类实现。update 函数循环调用就可不断的刷新画面了,此处要注意的是 opengl es 使用的函数,需要都安排在一个线程内,也就是说加载 shader、创建 program 等这些步骤都在同一个线程内调用完成。关于 egl 送显部分可以参考《OpenGL ES 与原生窗口之间的接口——EGL》。

#include "DisplayHandler.h"

//加入三维顶点数据 两个三角形组成正方形
const float vers[] = {
        1.0f, -1.0f, 0.0f,
        -1.0f, -1.0f, 0.0f,
        1.0f, 1.0f, 0.0f,
        -1.0f, 1.0f, 0.0f,
};

//加入材质坐标数据
const float txts[] = {
        1.0f, 0.0f,//右下
        0.0f, 0.0f,
        1.0f, 1.0f,
        0.0f, 1.0f
};

#define GET_STR(x) #x
//顶点着色器 glsl
static const char *vertexShader = GET_STR(
        attribute vec4 aPosition;//顶点坐标
        attribute vec2 aTexCoord;//材质顶点坐标
        varying vec2 vTexCoord;//输出的材质坐标 输出给片元着色器
        void main() {
            vTexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);
            gl_Position = aPosition;//显示顶点
        }
);
//片元着色器 NV12
//
// glsl
static const char *fragYUV = GET_STR(
        precision mediump float;//精度
        varying vec2 vTexCoord;//顶点着色器传递的坐标
        uniform sampler2D yTexture;//输入材质参数(不透明灰度,单像素)
        uniform sampler2D uvTexture;//输入材质参数
        void main() {
            vec3 yuv;
            vec3 rgb;
            yuv.r = texture2D(yTexture, vTexCoord).r;
            yuv.g = texture2D(uvTexture, vTexCoord).r - 0.5;
            yuv.b = texture2D(uvTexture, vTexCoord).a - 0.5;
            rgb = mat3(1.0, 1.0, 1.0,
                       0.0, 0.39465, 2.03211,
                       1.13983, -0.5806, 0.0) * yuv;
            //输出像素颜色
            gl_FragColor = vec4(rgb, 1.0);
        }
);

DisplayHandler::DisplayHandler() {
    videoWidth = 1920;
    videoHeight = 1080;

    glProgram = 0;

    eglSurface = nullptr;
    eglContext = nullptr;
    eglDisplay = nullptr;
}

bool DisplayHandler::initEGL(ANativeWindow *nwin) {
    //EGL
    //1  eglDisplay 显示
    eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (eglDisplay == EGL_NO_DISPLAY) {
        LOGE("get eglDisplay failed!");
        return false;
    }
    //初始化 后面两个参数是版本号
    if (EGL_TRUE != eglInitialize(eglDisplay, 0, 0)) {
        LOGE("eglInitialize failed!");
        return false;
    }
    //2  surface (关联原始窗口)
    //surface 配置
    //输出配置
    EGLConfig config;
    EGLint configNum;
    //输入配置
    EGLint configSpec[] = {
            EGL_RED_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE, 8,
            EGL_SURFACE_TYPE,
            EGL_WINDOW_BIT,
            EGL_NONE
    };

    if (EGL_TRUE != eglChooseConfig(eglDisplay, configSpec, &config, 1, &configNum)) {
        LOGE("eglChooseConfig failed!");
        return false;
    }
    //创建surface (关联原始窗口)
    eglSurface = eglCreateWindowSurface(eglDisplay, config, nwin, 0);

    if (eglSurface == EGL_NO_SURFACE) {
        LOGE("eglCreateWindowSurface failed!");
        return false;
    }

    //3  context 创建关联上下文
    const EGLint ctxAttr[] = {
            EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE
    };

    eglContext = eglCreateContext(eglDisplay, config, EGL_NO_CONTEXT, ctxAttr);
    if (eglContext == EGL_NO_CONTEXT) {
        LOGE("eglCreateContext failed!");
        return false;
    }
    //egl 关联 openGL
    if (EGL_TRUE != eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
        LOGE("eglMakeCurrent failed!");
        return false;
    }
    LOGI("EGL init success");
    return true;
}

void DisplayHandler::deinitEGL() {
    EGLBoolean success;
    if (eglDisplay != nullptr && eglSurface != nullptr) {
        success = eglDestroySurface(eglDisplay, eglSurface);
        if (!success) {
            LOGE("eglDestroySurface failure.");
        }
        eglSurface = nullptr;
    }

    if (eglDisplay != nullptr && eglContext != nullptr) {
        success = eglDestroyContext(eglDisplay, eglContext);
        if (!success) {
            LOGE("eglDestroyContext failure.");
        }
        eglContext = nullptr;

        success = eglTerminate(eglDisplay);
        if (!success) {
            LOGE("eglTerminate failure.");
        }
        eglDisplay = nullptr;
    }

    if (glProgram != 0) {
        glDeleteProgram(glProgram);
        glProgram = 0;
    }
}

//初始化着色器
GLint DisplayHandler::initShader(const char *code, GLint type) {
    GLuint shader;
    GLint compiled;
    // Create an empty shader object, which maintain the source code strings that define a shader
    shader = glCreateShader(type);
    if (shader == 0) {
        return 0;
    }
    // Replaces the source code in a shader object
    glShaderSource(shader, 1, &code, nullptr);
    // Compile the shader object
    glCompileShader(shader);
    // Check the shader object compile status
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);

    if (!compiled) {
        GLint infoLen = 0;

        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);

        if (infoLen > 1) {
            GLchar *infoLog = (GLchar *) malloc(sizeof(GLchar) * infoLen);

            glGetShaderInfoLog(shader, infoLen, nullptr, infoLog);
            LOGE("Error compiling shader:\n%s\n", infoLog);
            free(infoLog);
        }

        glDeleteShader(shader);
        return 0;
    }

    return shader;
}

GLuint DisplayHandler::loadProgram(const char *vShaderStr, const char *fShaderStr) {
    GLuint vertexShader;
    GLuint fragmentShader;
    GLuint programObject;
    GLint linked;
    // Load the vertex/fragment shaders
    vertexShader = initShader(vShaderStr, GL_VERTEX_SHADER);
    fragmentShader = initShader(fShaderStr, GL_FRAGMENT_SHADER);
    // Create the program object
    programObject = glCreateProgram();
    if (programObject == 0) {
        return 0;
    }
    // Attaches a shader object to a program object
    glAttachShader(programObject, vertexShader);
    glAttachShader(programObject, fragmentShader);
    // Bind vPosition to attribute 0
    glBindAttribLocation(programObject, 0, "vPosition");
    // Link the program object
    glLinkProgram(programObject);

    // Check the link status
    glGetProgramiv(programObject, GL_LINK_STATUS, &linked);

    if (!linked) {
        GLint infoLen = 0;

        glGetProgramiv(programObject, GL_INFO_LOG_LENGTH, &infoLen);

        if (infoLen > 1) {
            GLchar *infoLog = (GLchar *) malloc(sizeof(GLchar) * infoLen);

            glGetProgramInfoLog(programObject, infoLen, NULL, infoLog);
            LOGE("Error linking program:\n%s\n", infoLog);
            free(infoLog);
        }

        glDeleteProgram(programObject);
        return GL_FALSE;
    }
    // Free no longer needed shader resources
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    return programObject;
}

GLint DisplayHandler::createProgram() {
    GLuint programObject;
    // Load the shaders and get a linked program object
    programObject = loadProgram((const char *) vertexShader,
                                (const char *) fragYUV);
    if (programObject == 0) {
        return GL_FALSE;
    }
    // Store the program object
    glProgram = programObject;

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    glGenTextures(TEXTURE_NUM, mTextureID);
    for (int i = 0; i < TEXTURE_NUM; i++) {
        glBindTexture(GL_TEXTURE_2D, mTextureID[i]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    }

    //激活渲染程序
    glUseProgram(glProgram);
    //获取shader中的顶点变量
    GLuint apos = (GLuint) glGetAttribLocation(glProgram, "aPosition");
    glEnableVertexAttribArray(apos);
    //传递顶点
    /*
     * apos 传到哪
     * 每一个点有多少个数据
     * 格式
     * 是否有法线向量
     * 一个数据的偏移量
     * 12 顶点有三个值(x,y,z)float存储 每个有4个字节 每一个值的间隔是 3*4 = 12
     * ver 顶点数据
     * */
    glVertexAttribPointer(apos, 3, GL_FLOAT, GL_FALSE, 12, vers);

    GLuint atex = (GLuint) glGetAttribLocation(glProgram, "aTexCoord");
    glEnableVertexAttribArray(atex);
    glVertexAttribPointer(atex, 2, GL_FLOAT, GL_FLOAT, 8, txts);

    //设置纹理层
    glUniform1i(glGetUniformLocation(glProgram, "yTexture"), 0);//对于纹理第1层
    glUniform1i(glGetUniformLocation(glProgram, "uvTexture"), 1);//对于纹理第2层

    return 0;
}

void DisplayHandler::setVideoWH(int width, int height) {
    videoWidth = width;
    videoHeight = height;
}

int DisplayHandler::getVideoWidth() const {
    return videoWidth;
}

int DisplayHandler::getVideoHeight() const {
    return videoHeight;
}

void DisplayHandler::update(unsigned char *yuvBuf) {
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, mTextureID[0]);//绑定纹理,下面的属性针对这个纹理设置
    //设置纹理的格式和大小
    /*
     * GL_TEXTURE_2D
     * 显示细节的级别
     * 内部gpu 格式 亮度 灰度图
     * 宽
     * 高
     * 边框
     * 数据的像素格式
     * 像素的数据类型
     * 纹理数据
     * */
    glTexImage2D(GL_TEXTURE_2D,
                 0,//默认
                 GL_LUMINANCE,
                 videoWidth, videoHeight, //尺寸要是2的次方  拉伸到全屏
                 0,
                 GL_LUMINANCE,//数据的像素格式,要与上面一致
                 GL_UNSIGNED_BYTE,// 像素的数据类型
                 yuvBuf
    );

    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, mTextureID[1]);
    glTexImage2D(GL_TEXTURE_2D,
                 0,//默认
                 GL_LUMINANCE_ALPHA,
                 videoWidth / 2, videoHeight / 2, //尺寸要是2的次方  拉伸到全屏
                 0,
                 GL_LUMINANCE_ALPHA,//数据的像素格式,要与上面一致
                 GL_UNSIGNED_BYTE,// 像素的数据类型
                 yuvBuf + (videoWidth * videoHeight)
    );

    //三维绘制
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);//从0顶点开始 一共4个顶点
    //窗口显示
    eglSwapBuffers(eglDisplay, eglSurface);//交换buf
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/46037
推荐阅读
相关标签
  

闽ICP备14008679号