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
#include
#include
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.299R’ + 0.587G’ + 0.114B’
U’= -0.147R’ - 0.289G’ + 0.436B’ = 0.492*(B’- Y’)
V’= 0.615R’ - 0.515G’ - 0.100B’ = 0.877(R’- Y’)
R’ = Y’ + 1.140V’
G’ = Y’ - 0.394U’ - 0.581V’
B’ = Y’ + 2.032U’
YCbCr模型来源于YUV模型。YCbCr是 YUV 颜色空间的偏移版本.
应用:数字视频,ITU-R BT.601建议
Y’ = 0.257R’ + 0.504G’ + 0.098B’ + 16
Cb’ = -0.148R’ - 0.291G’ + 0.439B’ + 128
Cr’ = 0.439R’ - 0.368G’ - 0.071B’ + 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+v1.13983 , y-u0.39465-v0.58060 , y+u*2.03211
//输出像素颜色
gl_FragColor = vec4(rgb,1.0); // gl_FragColor 的颜色值的取值范围为 0-1 赋值给gl_FragColor时,如果颜色数据小于0会变成0,大于1会变成1 ?
}
###效果图