(七) EGL和OpenGLES Shader显示YUV视频

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huanghuangjin/article/details/81983389
EGL为OpenGL与系统窗口对应的适配层    官方文档 https://www.khronos.org/registry/EGL/sdk/docs/man/
    EGL在android中可以用c来调,也可以用java来调用

glsl提供了大量的内置函数

官网下载 ffmpeg builds 将 ffmpeg.exe 命令配置到环境变量 E:\mpeg\ffmpeg-4.0.2-cmd\bin
    E:\mpeg\videos 下执行命令 ffmpeg -i 1080.mp4 -pix_fmt yuv420p -s 424x240 out.yuv    将当前目录下的 mp4 视频转换为 yuv420p 格式(20多M转换成了200多M)

YUV420平面方式存储(下图是I420排列,其他存储排列形式参看 media.txt ):
这里写图片描述

代码(c++)

#include "common.hpp"
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <EGL/egl.h> // EGL

static bool isPlay;

#define get_str(x) #x // 已 define、# 形式定义字符串,这样写的好处是不用写字符串的 "" 符号了
static const char *vertShader = get_str( // 这样赋值字符串,数据类型需要是 const char * ,否则会报警告
    attribute vec4 aPosition; //顶点坐标
    attribute vec2 aTexCoord; //材质顶点坐标
    varying vec2 vTexCoord;   //输出的材质坐标
    void main()
    {
        vTexCoord = vec2(aTexCoord.x,1.0-aTexCoord.y);
        gl_Position = aPosition;
    }
);
static char fragShader[1024]; // 片元着色器源码

JNIEXPORT void JNICALL Java_hankin_hjmedia_mpeg_Mp8_11Activity_show(JNIEnv *env, jobject instance, jstring url_, jobject surface, jstring vertStr_, jstring fragStr_)
{
    isPlay = true;
    const char * url = env->GetStringUTFChars(url_, NULL);
    const char * vertStr = env->GetStringUTFChars(vertStr_, NULL); // 顶点着色器源码在此文件中写成字符串了,没用assets里的,它们是一样的
    const char * fragStr = env->GetStringUTFChars(fragStr_, NULL);
    char path[512];
    strcpy(path, url);
    strcpy(fragShader, fragStr); // 片元着色器源码
    env->ReleaseStringUTFChars(vertStr_, vertStr);
    env->ReleaseStringUTFChars(fragStr_, fragStr);
    env->ReleaseStringUTFChars(url_, url);
    if (path[0]=='\0' || fragShader[0]=='\0')
    {
        LOGE("peth or fragShader is empty.");
        return;
    }

    ANativeWindow * nwin = ANativeWindow_fromSurface(env, surface); // 获取原始窗口
    // 1、display  EGL创建步骤
    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); // 创建EGL显示设备,选择默认的
    if (display == EGL_NO_DISPLAY)
    {
        LOGE("eglGetDisplay failed.");
        return;
    }
    /*
         EGLAPI EGLBoolean EGLAPIENTRY eglInitialize ( // 初始化EGLdisplay
             EGLDisplay dpy, // display
             EGLint *major, // 主版本号
             EGLint *minor // 次版本号, 两个都传空表示使用默认值 2.0 ?
         );
     */
    if (EGL_TRUE != eglInitialize(display, NULL, NULL)) // 初始化EGLdisplay,后面两个参数为 主版本号与次版本号, 传空表示使用默认值
    {
        LOGE("eglInitialize failed.");
        return;
    }
    // 2、surface
    EGLConfig config; // 输出的config
    EGLint configNUm; // 输出的config的数量
    EGLint configSpec[] = { // 输入的
        EGL_RED_SIZE, 8, // EGL_RED_SIZE 表示 r 占 8位
        EGL_GREEN_SIZE, 8, // EGL_GREEN_SIZE 表示 g 占8位
        EGL_BLUE_SIZE, 8, // EGL_BLUE_SIZE 表示 b 占8位
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE // EGL_SURFACE_TYPE 类型对应的是 EGL_WINDOW_BIT ,最后的 EGL_NONE 表示数组的结尾处NULL,用于数组遍历结束判断
    };
    /*
         EGLAPI EGLBoolean EGLAPIENTRY eglChooseConfig ( // 设置egl surface配置项
             EGLDisplay dpy, // display
             const EGLint *attrib_list, // 输入的参数列表
             EGLConfig *configs, // 输出的配置项
             EGLint config_size, // 最多存储多少个输出的配置项
             EGLint *num_config // 实际存储的输出配置项的个数, <= config_size
         );
     */
    if (EGL_TRUE != eglChooseConfig(display, configSpec, &config, 1, &configNUm))
    {
        LOGE("eglChooseConfig failed.");
        return;
    }
    /*
         EGLAPI EGLSurface EGLAPIENTRY eglCreateWindowSurface ( // 创建 egl surface
             EGLDisplay dpy, // display
             EGLConfig config, // egl surface配置项
             EGLNativeWindowType win, // 本地窗口, EGLNativeWindowType 就是 ANativeWindow *
             const EGLint *attrib_list // 属性信息,用来设置版本号,传空表示默认版本号
         );
     */
    EGLSurface eglSurface = eglCreateWindowSurface(display, config, nwin, NULL);
    if (eglSurface == EGL_NO_SURFACE)
    {
        LOGE("eglCreateWindowSurface failed.");
        return;
    }
    // 3、context 通过EGL关联系统窗口与OpenGL的上下文
    const EGLint ctxAttr[] = {
            EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE // EGL_CONTEXT_CLIENT_VERSION,2  表示EGL版本号为2, EGL_NONE 表示数组的结尾处NULL,用于数组遍历结束判断
    };
    /*
         EGLAPI EGLContext EGLAPIENTRY eglCreateContext ( // 创建关联系统窗口与OpenGL的上下文
             EGLDisplay dpy, // display
             EGLConfig config, // 配置信息
             EGLContext share_context, // 多个显示设备共享EGLContext的时候使用,这里用不到共享,所以传 EGL_NO_CONTEXT
             const EGLint *attrib_list // EGL版本信息
         );
     */
    EGLContext eglContext = eglCreateContext(display, config, EGL_NO_CONTEXT, ctxAttr);
    if (eglContext == EGL_NO_CONTEXT)
    {
        LOGE("eglCreateContext failed.");
        return;
    }
    /*
         EGLAPI EGLBoolean EGLAPIENTRY eglMakeCurrent ( // 数据交互
             EGLDisplay dpy, // display
             EGLSurface draw, // 用来绘制的 EGLSurface
             EGLSurface read, // 用来读取的 EGLSurface
             EGLContext ctx // 上下文
         );
     */
    if (EGL_TRUE != eglMakeCurrent(display, eglSurface, eglSurface, eglContext)) // 至此,系统窗口与OpenGL真正关联起来了
    {
        LOGE("eglMakeCurrent failed.");
        return;
    }

    // 创建着色器程序
    GLuint glProgram = hjcreateGLProgram(vertShader, fragShader);
    glUseProgram(glProgram);

    //加入三维顶点数据 两个三角形组成正方形
    static 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, // 左上
    };
    GLuint apos = (GLuint) glGetAttribLocation(glProgram,"aPosition");
    glEnableVertexAttribArray(apos);
    //传递顶点
    glVertexAttribPointer(apos,3,GL_FLOAT,GL_FALSE,12,vers);

    //加入材质坐标数据
    static float txts[] = { // 纹理坐标,注意在frag中 1-y 了
            1.0f,0.0f, // 右下
            0.0f,0.0f,
            1.0f,1.0f,
            0.0f,1.0f
    };
    GLuint atex = (GLuint)glGetAttribLocation(glProgram,"aTexCoord");
    glEnableVertexAttribArray(atex);
    glVertexAttribPointer(atex,2,GL_FLOAT,GL_FALSE,8,txts);

    int width = 424; // 输入的out.yuv的宽高
    int height = 240;

    //材质纹理初始化
    //设置纹理层
    glUniform1i( glGetUniformLocation(glProgram,"yTexture"),0); //对于纹理第1层,纹理单元0
    glUniform1i( glGetUniformLocation(glProgram,"uTexture"),1); //对于纹理第2层
    glUniform1i( glGetUniformLocation(glProgram,"vTexture"),2); //对于纹理第3层

    //创建opengl纹理
    GLuint texts[3] = {0};
    //创建三个纹理
    glGenTextures(3,texts);

    //设置纹理属性
    glBindTexture(GL_TEXTURE_2D,texts[0]);
    //缩小的过滤器
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    //设置纹理的格式和大小
    /*
        GL_APHPA            按照ALPHA值存储纹理单元
        GL_LUMINANCE        按照亮度值存储纹理单元,即YUV的Y
        GL_LUMINANCE_ALPHA  按照亮度和alpha值存储纹理单元
        GL_RGB              按照RGB成分存储纹理单元
        GL_RGBA             按照RGBA成分存储纹理单元
     */
    glTexImage2D(GL_TEXTURE_2D,
                 0,           //细节基本 0默认
                 GL_LUMINANCE,//纹理保存到gpu内部的格式 亮度,灰度图,即YUV的Y
                 width,height, //拉升到全屏
                 0,             //边框
                 GL_LUMINANCE,//加载的数据的像素格式 亮度,灰度图 要与上面一致
                 GL_UNSIGNED_BYTE, //像素的数据类型
                 NULL                    //纹理的数据,暂设空,后面后纹理数据交互的方式
    );

    //设置纹理属性
    glBindTexture(GL_TEXTURE_2D,texts[1]);
    //缩小的过滤器
    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,           //细节基本 0默认
                 GL_LUMINANCE,//gpu内部格式 亮度,灰度图
                 width/2,height/2, //拉升到全屏, U V 对应的纹理的尺寸要除以2 是因为 U V 的长度是 Y 的一半
                 0,             //边框
                 GL_LUMINANCE,//数据的像素格式 亮度,灰度图 要与上面一致
                 GL_UNSIGNED_BYTE, //像素的数据类型
                 NULL                    //纹理的数据
    );

    //设置纹理属性
    glBindTexture(GL_TEXTURE_2D,texts[2]);
    //缩小的过滤器
    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,           //细节基本 0默认
                 GL_LUMINANCE,//gpu内部格式 亮度,灰度图
                 width/2,height/2, //拉升到全屏
                 0,             //边框
                 GL_LUMINANCE,//数据的像素格式 亮度,灰度图 要与上面一致
                 GL_UNSIGNED_BYTE, //像素的数据类型
                 NULL                    //纹理的数据
    );





    //////////////////////////////////////////////////////
    ////纹理的修改和显示
    unsigned char *buf[3] = {0};
    buf[0] = new unsigned char[width*height];
    buf[1] = new unsigned char[width*height/4];
    buf[2] = new unsigned char[width*height/4];

    FILE * fp = fopen(path, "rb"); // 文件是以 ffmpeg 的 YUV420P 方式存放数据的
    if (!fp)
    {
        LOGE("fopen failed.");
        return;
    }

    for(int i = 0; i<10000;i++)
    {
        if (!isPlay) break;

//        memset(buf[0],i,width*height); // 显示的效果为 渐变色
//        memset(buf[1],i,width*height/4);
//        memset(buf[2],i,width*height/4);

        //420p   yyyyyyyy uu vv
        if(feof(fp) == 0)
        {
            //yyyyyyyy
            fread(buf[0],1,width*height,fp);
            fread(buf[1],1,width*height/4,fp);
            fread(buf[2],1,width*height/4,fp);
            LOGV("%u, %u, %u", buf[0][123], buf[1][21], buf[2][75]); // 163, 126, 130    随便选取的一个打印的数据
        }
        else
        {
            fseek(fp, 0, SEEK_SET); // 文件内部指针跳转到开始地方,循环播放
            continue;
        }





        //激活第1层纹理,绑定到创建的opengl纹理
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D,texts[0]);
        /*
             GL_APICALL void GL_APIENTRY glTexSubImage2D ( // 修改纹理的内容,此函数常用作视频显示,如果频繁创建新的纹理比修改原纹理性能消耗要大很多
                 GLenum target, // 纹理类型,与生成纹理时设置的类型要一致
                 GLint level, // mipmap等级
                 GLint xoffset, // 从原纹理左上角偏移 x 开始修改纹理数据
                 GLint yoffset, // 从原纹理左上角偏移 y 开始修改纹理数据
                 GLsizei width, // 要修改的图像宽高,像素修改的范围在原图之外的并不受影响
                 GLsizei height,
                 GLenum format, // 表示要修改的图像的数据格式
                 GLenum type, // 表示要修改的图像的类型
                 const void *pixels // 要修改的图像的数据
             );
         */
        glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width,height,GL_LUMINANCE,GL_UNSIGNED_BYTE,buf[0]);



        //激活第2层纹理,绑定到创建的opengl纹理
        glActiveTexture(GL_TEXTURE0+1);
        glBindTexture(GL_TEXTURE_2D,texts[1]);
        //替换纹理内容
        glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width/2,height/2,GL_LUMINANCE,GL_UNSIGNED_BYTE,buf[1]);


        //激活第2层纹理,绑定到创建的opengl纹理
        glActiveTexture(GL_TEXTURE0+2);
        glBindTexture(GL_TEXTURE_2D,texts[2]);
        //替换纹理内容
        glTexSubImage2D(GL_TEXTURE_2D,0,0,0,width/2,height/2,GL_LUMINANCE,GL_UNSIGNED_BYTE,buf[2]);

        //三维绘制
        glDrawArrays(GL_TRIANGLE_STRIP,0,4);
        //窗口显示
        eglSwapBuffers(display,eglSurface);


    }

    fclose(fp);
    eglDestroyContext(display, eglContext); // 释放内存
    eglDestroySurface(display, eglSurface); // 释放内存
    ANativeWindow_release(nwin);
    LOGI("mp8_1 end.");
}

JNIEXPORT void JNICALL Java_hankin_hjmedia_mpeg_Mp8_11Activity_end(JNIEnv *env, jobject instance) // 结束播放
{
    isPlay = false;
}

代码(shader)

precision mediump float;    //精度
varying vec2 vTexCoord;     //顶点着色器传递的坐标
uniform sampler2D yTexture; //输入的材质(不透明灰度,单像素)
uniform sampler2D uTexture;
uniform sampler2D vTexture;
void main()
{
    vec3 yuv;
    vec3 rgb;
    yuv.r = texture2D(yTexture,vTexCoord).r;
    yuv.g = texture2D(uTexture,vTexCoord).r - 0.5; // - 0.5 为了四舍五入?   以YUV420P纹理方式输入的数据,texture2D().r 出来的颜色数据值的范围不是 0-1 而是 0-255 ?
    yuv.b = texture2D(vTexture,vTexCoord).r - 0.5;
    /*
        YUV与RGB互转,先区分一下YUV和YCbCr
            YUV色彩模型来源于RGB模型,
            该模型的特点是将亮度和色度分离开,从而适合于图像处理领域。
            应用:模拟领域
                Y'= 0.299*R' + 0.587*G' + 0.114*B'
                U'= -0.147*R' - 0.289*G' + 0.436*B' = 0.492*(B'- Y')
                V'= 0.615*R' - 0.515*G' - 0.100*B' = 0.877*(R'- Y')
                R' = Y' + 1.140*V'
                G' = Y' - 0.394*U' - 0.581*V'
                B' = Y' + 2.032*U'
            YCbCr模型来源于YUV模型。YCbCr是 YUV 颜色空间的偏移版本.
            应用:数字视频,ITU-R BT.601建议
                Y’ = 0.257*R' + 0.504*G' + 0.098*B' + 16
                Cb' = -0.148*R' - 0.291*G' + 0.439*B' + 128
                Cr' = 0.439*R' - 0.368*G' - 0.071*B' + 128
                R' = 1.164*(Y’-16) + 1.596*(Cr'-128)
                G' = 1.164*(Y’-16) - 0.813*(Cr'-128) - 0.392*(Cb'-128)
                B' = 1.164*(Y’-16) + 2.017*(Cb'-128)
            PS: 上面各个符号都带了一撇,表示该符号在原值基础上进行了伽马校正,伽马校正有助于弥补在抗锯齿的过程中,线性分配伽马值所带来的细节损失,使图像细节更加丰富。
                在没有采用伽马校正的情况下,暗部细节不容易显现出来,而采用了这一图像增强技术以后,图像的层次更加明晰了。
            所以说H264里面的YUV应属于YCbCr, 也可以点 http://www.fourcc.org/fccyvrgb.php 获得其他一些信息.
    */
    rgb = mat3(1.0,     1.0,      1.0,
               0.0,     -0.39465, 2.03211,
               1.13983, -0.58060, 0.0) * yuv; // 矩阵的形式,将 YUV转换为RGB      y+v*1.13983 , y-u*0.39465-v*0.58060 , y+u*2.03211
    //输出像素颜色
    gl_FragColor = vec4(rgb,1.0); // gl_FragColor 的颜色值的取值范围为 0-1    赋值给gl_FragColor时,如果颜色数据小于0会变成0,大于1会变成1 ?
}

效果图

这里写图片描述

猜你喜欢

转载自blog.csdn.net/huanghuangjin/article/details/81983389