YUV回顾

记住在音视频基础知识介绍中,笔者专门介绍过YUV的相关知识,能够参考: 《音视频基础知识-YUV图画》

YUV数据量相比RGB较小,因而YUV适用于传输,但是YUV图不能直接用于显现,需求转化为RGB格局才干显现,因而YUV数据烘托实际上便是运用Opengl ES将YUV数据转化程RGB数据,然后显现出来的进程。

也便是说Opengl ES之所以能烘托YUV数据其实便是运用了Opengl强壮的并行计算能力,快速地将YUV数据转化程了RGB数据。

本文首发于微信公总号号:思想觉悟

更多关于音视频、FFmpeg、Opengl、C++的原创文章请重视微信大众号:思想觉悟

YUV的格局比较多,咱们今日就以YUV420SP为例,而YUV420SP又分为NV12NV21两种,因而今日咱们的主题便是怎么运用Opengl ES对NV12NV21数据进行烘托显现。

在着色器中运用texture2D对YUV数据进行归一化处理后Y数据的映射规模是0到1,而U和V的数据映射规模是-0.5到0.5。

由于YUV格局图画 UV 重量的默认值别离是 127 ,Y 重量默认值是 0 ,8 个 bit 位的取值规模是 0 ~ 255,由于在 shader 中纹路采样值需求进行归一化,所以 UV 重量的采样值需求别离减去 0.5 ,确保 YUV 到 RGB 正确转化。

YUV数据预备

首要咱们能够运用ffmpeg指令行将一张png图片转化成YUV格局的图片:

ffmpeg -i 图片名称.png -s 图片宽x图片高 -pix_fmt nv12或许nv21 输出名称.yuv)

经过上面这个指令行咱们就能够将一张图片转化成yuv格局的图片,此刻咱们能够运用软件YUVViewer看下你转化的图片对不对,假如自身转化出来的图片便是错的,那么后边的程序就白搭了…

留意:转化图片的宽高最好是2的幂次方,笔者测试了下发现假如宽高不是2的幂次方的话虽然能正常转化,但是检查的时候要么有色差,要么有缺陷,也有或许正常。

又或许你能够极客一点,直接运用ffmpeg代码解码视频的办法取得YUV数据并保存,这个能够参考笔者之前的文章:

《FFmpeg连载3-视频解码》

一起在上面的文章中笔者也介绍了经过ffplay指令行的办法检查YUV数据的办法。

YUV数据烘托

YUV 烘托步骤:

  • 生成 2 个纹路,别离用于承载Y数据和UV数据,编译链接着色器程序;

NV21和NV12格局的YUV数据是只有两个平面的,它们的摆放次序是YYYY UVUV或许YYYY VUVU因而咱们的片元着色器需求两个纹路采样。

  • 确定纹路坐标及对应的极点坐标;
  • 别离加载 NV21 的两个 Plane 数据到 2 个纹路,加载纹路坐标和极点坐标数据到着色器程序;
  • 制作。

YUV与RGB的转化格局图:

Opengl ES之YUV数据渲染

OpenGLES的内置矩阵实际上是一列一列地构建的,比如YUV和RGB的转化矩阵的构建是:

// 标准转化,放弃了部分小数精度
mat3 convertMat = mat3(1.0, 1.0, 1.0,      //榜首列
                       0.0,-0.3381.732//第二列
                       1.371,-0.6980.0);//第三列

OpenGLES 完成 YUV 烘托需求用到 GL_LUMINANCE 和 GL_LUMINANCE_ALPHA 格局的纹路,其间 GL_LUMINANCE 纹路用来加载 NV21 Y Plane 的数据,GL_LUMINANCE_ALPHA 纹路用来加载 UV Plane 的数据。

废话少说,show me the code

YUVRenderOpengl.h

#ifndef NDK_OPENGLES_LEARN_YUVRENDEROPENGL_H
#define NDK_OPENGLES_LEARN_YUVRENDEROPENGL_H
#include "BaseOpengl.h"
class YUVRenderOpengl: public BaseOpengl{
public:
    YUVRenderOpengl();
    virtual ~YUVRenderOpengl();
    virtual void onDraw() override;
    // 设置yuv数据
    virtual void setYUVData(void *y_data,void *uv_data, int width, int height, int yuvType);
private:
    GLint positionHandle{-1};
    GLint textureHandle{-1};
    GLint y_textureSampler{-1};
    GLint uv_textureSampler{-1};
    GLuint y_textureId{0};
    GLuint uv_textureId{0};
};
#endif //NDK_OPENGLES_LEARN_YUVRENDEROPENGL_H

YUVRenderOpengl.cpp


#include "YUVRenderOpengl.h"
#include "../utils/Log.h"
// 极点着色器
static const char *ver = "#version 300 es\n"
                         "in vec4 aPosition;\n"
                         "in vec2 aTexCoord;\n"
                         "out vec2 TexCoord;\n"
                         "void main() {\n"
                         "  TexCoord = aTexCoord;\n"
                         "  gl_Position = aPosition;\n"
                         "}";
// 片元着色器 nv12
//static const char *fragment = "#version 300 es\n"
//                              "precision mediump float;\n"
//                              "out vec4 FragColor;\n"
//                              "in vec2 TexCoord;\n"
//                              "uniform sampler2D y_texture; \n"
//                              "uniform sampler2D uv_texture;\n"
//                              "void main()\n"
//                              "{\n"
//                              "vec3 yuv;\n"
//                              "yuv.x = texture(y_texture, TexCoord).r;\n"
//                              "yuv.y = texture(uv_texture, TexCoord).r-0.5;\n"
//                              "yuv.z = texture(uv_texture, TexCoord).a-0.5;\n"
//                              "vec3 rgb =mat3( 1.0,1.0,1.0,\n"
//                              "0.0,-0.344,1.770,1.403,-0.714,0.0) * yuv;\n"
//                              "FragColor = vec4(rgb, 1);\n"
//                              "}";
/**
 *  仅仅是以下两句不同而已
 *  "yuv.y = texture(uv_texture, TexCoord).r-0.5;\n"
 *  "yuv.z = texture(uv_texture, TexCoord).a-0.5;\n"
 */
// 片元着色器nv21 仅仅是
static const char *fragment = "#version 300 es\n"
                              "precision mediump float;\n"
                              "out vec4 FragColor;\n"
                              "in vec2 TexCoord;\n"
                              "uniform sampler2D y_texture; \n"
                              "uniform sampler2D uv_texture;\n"
                              "void main()\n"
                              "{\n"
                              "vec3 yuv;\n"
                              "yuv.x = texture(y_texture, TexCoord).r;\n"
                              "yuv.y = texture(uv_texture, TexCoord).a-0.5;\n"
                              "yuv.z = texture(uv_texture, TexCoord).r-0.5;\n"
                              "vec3 rgb =mat3( 1.0,1.0,1.0,\n"
                              "0.0,-0.344,1.770,1.403,-0.714,0.0) * yuv;\n"
                              "FragColor = vec4(rgb, 1);\n"
                              "}";
// 运用制作两个三角形组成一个矩形的办法(三角形带)
// 榜首第二第三个点组成一个三角形,第二第三第四个点组成一个三角形
const static GLfloat VERTICES[] = {
        0.5f,-0.5f, // 右下
        0.5f,0.5f, // 右上
        -0.5f,-0.5f, // 左下
        -0.5f,0.5f // 左上
};
// 贴图纹路坐标(参考手机屏幕坐标体系,原点在左上角)
//由于对一个OpenGL纹路来说,它没有内在的方向性,因而咱们能够运用不同的坐标把它定向到任何咱们喜欢的方向上,然而大多数计算机图画都有一个默认的方向,它们通常被规定为y轴向下,X轴向右
const static GLfloat TEXTURE_COORD[] = {
        1.0f,1.0f, // 右下
        1.0f,0.0f, // 右上
        0.0f,1.0f, // 左下
        0.0f,0.0f // 左上
};
YUVRenderOpengl::YUVRenderOpengl() {
    initGlProgram(ver,fragment);
    positionHandle = glGetAttribLocation(program,"aPosition");
    textureHandle = glGetAttribLocation(program,"aTexCoord");
    y_textureSampler = glGetUniformLocation(program,"y_texture");
    uv_textureSampler = glGetUniformLocation(program,"uv_texture");
    LOGD("program:%d",program);
    LOGD("positionHandle:%d",positionHandle);
    LOGD("textureHandle:%d",textureHandle);
    LOGD("y_textureSampler:%d",y_textureSampler);
    LOGD("uv_textureSampler:%d",uv_textureSampler);
}
YUVRenderOpengl::~YUVRenderOpengl() {
}
void YUVRenderOpengl::setYUVData(void *y_data, void *uv_data, int width, int height, int yuvType) {
    // 预备y数据纹路
    glGenTextures(1, &y_textureId);
    glActiveTexture(GL_TEXTURE2);
    glUniform1i(y_textureSampler, 2);
    // 绑定纹路
    glBindTexture(GL_TEXTURE_2D, y_textureId);
    // 为当时绑定的纹路对象设置盘绕、过滤办法
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, y_data);
    // 生成mip贴图
    glGenerateMipmap(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, y_textureId);
    // 解绑定
    glBindTexture(GL_TEXTURE_2D, 0);
    // 预备uv数据纹路
    glGenTextures(1, &uv_textureId);
    glActiveTexture(GL_TEXTURE3);
    glUniform1i(uv_textureSampler, 3);
    // 绑定纹路
    glBindTexture(GL_TEXTURE_2D, uv_textureId);
    // 为当时绑定的纹路对象设置盘绕、过滤办法
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // 留意宽高
    // 留意要运用 GL_LUMINANCE_ALPHA
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, width/2, height/2, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, uv_data);
    // 生成mip贴图
    glGenerateMipmap(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, uv_textureId);
    // 解绑定
    glBindTexture(GL_TEXTURE_2D, 0);
}
void YUVRenderOpengl::onDraw() {
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(program);
    // 激活纹路
    glActiveTexture(GL_TEXTURE2);
    // 绑定纹路
    glBindTexture(GL_TEXTURE_2D, y_textureId);
    glUniform1i(y_textureSampler, 2);
    // 激活纹路
    glActiveTexture(GL_TEXTURE3);
    // 绑定纹路
    glBindTexture(GL_TEXTURE_2D, uv_textureId);
    glUniform1i(uv_textureSampler, 3);
    /**
     * size 几个数字表明一个点,显现是两个数字表明一个点
     * normalized 是否需求归一化,不必,这里现已归一化了
     * stride 步长,接连极点之间的间隔,假如极点直接是接连的,也可填0
     */
    // 启用极点数据
    glEnableVertexAttribArray(positionHandle);
    glVertexAttribPointer(positionHandle,2,GL_FLOAT,GL_FALSE,0,VERTICES);
    // 纹路坐标
    glEnableVertexAttribArray(textureHandle);
    glVertexAttribPointer(textureHandle,2,GL_FLOAT,GL_FALSE,0,TEXTURE_COORD);
    // 4个极点制作两个三角形组成矩形
    glDrawArrays(GL_TRIANGLE_STRIP,0,4);
    glUseProgram(0);
    // 禁用极点
    glDisableVertexAttribArray(positionHandle);
    if(nullptr != eglHelper){
        eglHelper->swapBuffers();
    }
    glBindTexture(GL_TEXTURE_2D, 0);
}

留意看着色器代码的注释,NV12和NV21的烘托仅仅是着色器代码有细微不同而已。

YUVRenderActivity.java

public class YUVRenderActivity extends BaseGlActivity {
    // 留意改成你自己图片的宽高
    private int yuvWidth = 640;
    private int yuvHeight = 428;
    private String nv21Path;
    private String nv12Path;
    private Handler handler = new Handler(Looper.getMainLooper());
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 留意申请磁盘写权限
        // 拷贝资源
        nv21Path = getFilesDir().getAbsolutePath() + "/nv21.yuv";
        FileUtils.copyAssertToDest(this,"nv21.yuv",nv21Path);
        nv12Path = getFilesDir().getAbsolutePath() + "/nv12.yuv";
        FileUtils.copyAssertToDest(this,"nv12.yuv",nv12Path);
    }
    @Override
    public BaseOpengl createOpengl() {
        YUVRenderOpengl yuvRenderOpengl = new YUVRenderOpengl();
        return yuvRenderOpengl;
    }
    @Override
    protected void onResume() {
        super.onResume();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                // 留意nv12和nv21的偏远着色器有点不一样的,需求手动改下调试  YUVRenderOpengl.cpp
//                if(!TextUtils.isEmpty(nv12Path)){
//                    loadYuv(nv12Path,BaseOpengl.YUV_DATA_TYPE_NV12);
//                }
                if(!TextUtils.isEmpty(nv21Path)){
                    loadYuv(nv21Path,BaseOpengl.YUV_DATA_TYPE_NV21);
                }
            }
        },200);
    }
    @Override
    protected void onStop() {
        handler.removeCallbacksAndMessages(null);
        super.onStop();
    }
    private void loadYuv(String path,int yuvType){
        try {
            InputStream inputStream = new FileInputStream(new File(path));
            Log.v("fly_learn_opengl","---length:" + inputStream.available());
            byte[] yData = new byte[yuvWidth * yuvHeight];
            inputStream.read(yData,0,yData.length);
            byte[] uvData = new byte[yuvWidth * yuvHeight / 2];
            inputStream.read(uvData,0,uvData.length);
            Log.v("fly_learn_opengl","---read:" + (yData.length + uvData.length) + "available:" + inputStream.available());
            myGLSurfaceView.setYuvData(yData,uvData,yuvWidth,yuvHeight);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这个主要看懂loadYuv办法,对于YUV数据的读取即可。

思考

都说YUV的格局较多,本文咱们介绍了怎么运用Opengl ES烘托YUV420SP数据,那么对于YUV420P数据,运用Opengl ES怎么烘托呢?欢迎重视评论回答沟通。

专栏系列

Opengl ES之EGL环境搭建
Opengl ES之着色器
Opengl ES之三角形制作
Opengl ES之四边形制作
Opengl ES之纹路贴图
Opengl ES之VBO和VAO
Opengl ES之EBO
Opengl ES之FBO
Opengl ES之PBO

重视我,一起前进,人生不止coding!!!