OpenGL ES 送显 YUV 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 中 Y、U、V 三個平面是分開的。而 NV12 中 Y 是一個平面,UV 共同組成另一個平面。
不難看出 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;yuv.r 就是從紋理中拿到的 Y 分量,yuv.g 相應的是從紋理中拿到的 U 分量,yuv.b 就是 V 分量。
mat3 3x3 矩陣中的系數就是 YUV 轉 RGB 公式內的轉換系數。
OpenGL ES 送顯 NV12 完整流程如下:
具體代碼如下:
#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 2class 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以下是具體 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 關聯 openGLif (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 shadershader = glCreateShader(type);if (shader == 0) {return 0;}// Replaces the source code in a shader objectglShaderSource(shader, 1, &code, nullptr);// Compile the shader objectglCompileShader(shader);// Check the shader object compile statusglGetShaderiv(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 shadersvertexShader = initShader(vShaderStr, GL_VERTEX_SHADER);fragmentShader = initShader(fShaderStr, GL_FRAGMENT_SHADER);// Create the program objectprogramObject = glCreateProgram();if (programObject == 0) {return 0;}// Attaches a shader object to a program objectglAttachShader(programObject, vertexShader);glAttachShader(programObject, fragmentShader);// Bind vPosition to attribute 0glBindAttribLocation(programObject, 0, "vPosition");// Link the program objectglLinkProgram(programObject);// Check the link statusglGetProgramiv(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 resourcesglDeleteShader(vertexShader);glDeleteShader(fragmentShader);return programObject; }GLint DisplayHandler::createProgram() {GLuint programObject;// Load the shaders and get a linked program objectprogramObject = loadProgram((const char *) vertexShader,(const char *) fragYUV);if (programObject == 0) {return GL_FALSE;}// Store the program objectglProgram = 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 }總結
以上是生活随笔為你收集整理的OpenGL ES 送显 YUV NV12的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java调用有道翻译接口
- 下一篇: 2020年美赛C题(数据分析题)O奖论文