完整代码,一切尽在注释中
extern "C"
JNIEXPORT void JNICALL
Java_com_rzm_ffmpegplayer_FFmpegPlayer_initOpenGL(JNIEnv *env, jobject instance, jstring url_,
jobject surface) {
const char *url = env->GetStringUTFChars(url_, 0);
FILE *fp = fopen(url, "rb");
if (!fp) {
LOGE("open file %s failed!", url);
return;
}
/**************************EGL初始化********************************************/
//1.创建渲染窗口
ANativeWindow *aNativeWindow = ANativeWindow_fromSurface(env, surface);
//2.EGL Display创建
//EGL提供了平台无关类型EGLDisplay表示窗口。定义EGLNativeDisplayType是为了匹配原生窗口系统的显示类型,
// 对于Windows,EGLNativeDisplayType被定义为HDC,
// 对于Linux系统,被定义为Display*类型,
// 对于Android系统,定义为ANativeWindow *类型,
// 为了方便的将代码转移到不同的操作系统上,应该传入EGL_DEFAULT_DISPLAY,返回与默认原生窗口的连接。
// 如果连接不可用,则返回EGL_NO_DISPLAY
EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (eglDisplay == EGL_NO_DISPLAY) {
LOGE("eglGetDisplay failed%d", eglGetError());
return;
}
//3.初始化Display
//创建与本地原生窗口的连接后需要初始化EGL,使用函数eglInitialize进行初始化操作。如果 EGL 不能初始化,
// 它将返回EGL_FALSE,并将EGL错误码设置为EGL_BAD_DISPLAY表示制定了不合法的EGLDisplay,
// 或者EGL_NOT_INITIALIZED表示EGL不能初始化。使用函数eglGetError用来获取最近一次调用EGL函数出错的错误代码
//参数
//EGLDisplay display 创建的EGL连接
//EGLint *majorVersion 返回EGL主板版本号
//EGLint *minorVersion 返回EGL次版本号
if (EGL_TRUE != eglInitialize(eglDisplay, 0, 0)) {
LOGE("eglInitialize failed");
return;
}
//3. surface窗口配置
//窗口配置有两种方式:一种方式是使用eglGetConfigs函数获取底层窗口系统支持的所有EGL表面配置(Config),然后再使用
// eglGetConfigAttrib依次查询每个EGLConfig相关的信息,Config有众多的Attribute,这些Attribute决定FrameBuffer
// 的格式和能力,通过eglGetConfigAttrib ()来读取,但不能修改。EGLConfig包含了渲染表面的所有信息,包括可用颜色、
// 缓冲区等其他特性。
// 另一种方式是指定我们需要的渲染表面配置,让EGL自己选择一个符合条件的EGLConfig配置。eglChooseChofig
// 调用成功返回EGL_TRUE,失败时返回EGL_FALSE,如果attribList包含了未定义的EGL属性,或者属性值不合法,
// EGL代码被设置为EGL_BAD_ATTRIBUTR
//Config实际就是FrameBuffer的参数
EGLConfig eglConfig;
//attribList参数在EGL函数中可以为null或者指向一组以EGL_NONE结尾的键对值
//通常以id,value依次存放,对于个别标识性的属性可以只有id,没有value
EGLint configAttr[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_SURFACE_TYPE,
EGL_WINDOW_BIT,
EGL_NONE
};
EGLint eglConfigNum;
//参数
//EGLDisplay dpy 创建的和本地窗口系统的连接
//const EGLint *attrib_list, 指定渲染表面的参数列表,可以为null
//EGLConfig *configs 调用成功,返会符合条件的EGLConfig列表
//EGLint config_size, 最多返回的符合条件的EGLConfig个数
//EGLint *num_config 实际返回的符合条件的EGLConfig个数
if (EGL_TRUE != eglChooseConfig(eglDisplay, configAttr, &eglConfig, 1, &eglConfigNum)) {
LOGE("eglChooseConfig failed");
return;
}
//4. 创建surface
// 有了符合条件的EGLConfig后,就可以通过eglCreateWindowSurface函数创建渲染表面。使用这个函数的前提是要使
// 用原生窗口系统提供的API创建一个窗口。eglCreateWindowSurface中attribList一般可以使用null即可。
// 函数调用失败会返回EGL_NO_SURFACE,并设置对应的错误码
//使用eglCreateWindowSurface函数创建在窗口上的渲染表面,此外还可以使用eglCreatePbufferSurface创建
// 屏幕外渲染表面(Pixel Buffer 像素缓冲区)。使用Pbuffer一般用于生成纹理贴图,不过该功能已经被
// FrameBuffer替代了,使用帧缓冲对象的好处是所有的操作都由OpenGL ES来控制。使用Pbuffer的方法和前面创建
// 窗口渲染表面一样,需要改动的地方是在选取EGLConfig时,增加EGL_SURFACE_TYPE参数使其值包含
// EGL_PBUFFER_BIT。而该参数默认值为EGL_WINDOW_BIT。
//EGLSurface eglCreatePbufferSurface( EGLDisplay display, EGLConfig config, EGLint const * attrib_list // 指定像素缓冲区属性列表 );
//Surface实际上就是一个FrameBuffer
EGLSurface eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, aNativeWindow, 0);
if (eglSurface == EGL_NO_SURFACE) {
LOGE("eglCreateWindowSurface failed");
return;
}
//4 创建关联的上下文
//使用eglCreateContext为当前的渲染API创建EGL渲染上下文,返回一个上下文,当前的渲染API是由函数eglBindAPI
// 设置的。OpenGL ES是一个状态机,用一系列变量描述OpenGL ES当前的状态如何运行,我们通常使用如下途径去更改
// OpenGL状态:设置选项,操作缓冲。最后,我们使用当前OpenGL上下文来渲染。比如我想告诉OpenGL ES接下来要绘制
// 三角形,可以通过一些上下文变量来改变OpenGL ES的状态,一旦改变了OpenGL ES的状态为绘制三角形,下一个命令
// 就会画出三角形。通过这些状态设置函数就会改变上下文,接下来的操作总会根据当前上下文的状态来执行,除非再次
// 重新改变状态。
const EGLint ctxAttr[] = {
EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE
};
EGLContext eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, ctxAttr);
if (eglContext == EGL_NO_CONTEXT) {
LOGE("eglCreateContext failed!");
return;
}
//5.指定某个EGLContext为当前上下文。使用eglMakeCurrent函数进行当前上下文的绑定。一个程序可能创建多个EGLContext,
// 所以需要关联特定的EGLContext和渲染表面,一般情况下两个EGLSurface参数设置成一样的。
if (EGL_TRUE != eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
LOGE("eglMakeCurrent failed!");
return;
}
LOGI("EGL Init Success!");
/**************************EGL初始化********************************************/
/**************************shader初始化********************************************/
//定点shader初始化
GLint vshader = initShader(vertexShader, GL_VERTEX_SHADER);
//片元yuv420 shader初始化
GLint fshader = initShader(fragYUV420P, GL_FRAGMENT_SHADER);
/**************************shader初始化********************************************/
/**************************渲染程序初始化********************************************/
//7.使用OpenGL相关的API进行绘制操作。.....
//创建渲染程序
GLint program = glCreateProgram();
if (program == 0) {
LOGE("glCreateProgram failed!");
return;
}
//渲染程序中加入着色器代码
glAttachShader(program, vshader);
glAttachShader(program, fshader);
//链接程序
glLinkProgram(program);
GLint status = 0;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status != GL_TRUE) {
LOGE("glLinkProgram failed!");
return;
}
glUseProgram(program);
LOGI("glLinkProgram success!");
/////////////////////////////////////////////////////////////
//加入三维顶点数据 两个三角形组成正方形
//顶点坐标系描述了GopenGL的绘制范围,他以绘制中心为原点,在2D图形下,左边界为到x -1,右边界到x 1,上边界到y 1
//下边界到y -1,3D下同样道理。定点坐标系就是OpenGL的绘制区间
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(program, "aPosition");
glEnableVertexAttribArray(apos);
//传递顶点 取3个数据,跳转12个字节位(3个数据)再取另外3个数据,这是实现块状数据存储的关键,很多函数里都有这个参数,通常写作int stride
glVertexAttribPointer(apos, 3, GL_FLOAT, GL_FALSE, 12, vers);
//加入纹理坐标数据
//纹理坐标的坐标系以纹理左下角为坐标原点,向右为x正轴方向,向上为y轴正轴方向。他的总长度是1。即纹理图片的
// 四个角的坐标分别是:(0,0)、(1,0)、(0,1)、(1,1),分别对应左下、右下、左上、右上四个顶点。
static float txts[] = {
1.0f, 0.0f, //右下
0.0f, 0.0f, //左下
1.0f, 1.0f, //右上
0.0, 1.0 //左上
};
GLuint atex = (GLuint) glGetAttribLocation(program, "aTexCoord");
glEnableVertexAttribArray(atex);
glVertexAttribPointer(atex, 2, GL_FLOAT, GL_FALSE, 8, txts);
LOGI("glVertexAttribPointer success");
/**************************渲染程序传递数据********************************************/
/**************************纹理设置********************************************/
int width = 176;
int height = 144;
width = 352;
height = 288;
//材质纹理初始化
//设置纹理层
//对于纹理第1层
glUniform1i(glGetUniformLocation(program, "yTexture"), 0);
//对于纹理第2层
glUniform1i(glGetUniformLocation(program, "uTexture"), 1);
//对于纹理第3层
glUniform1i(glGetUniformLocation(program, "vTexture"), 2);
//创建opengl纹理
GLuint texts[3] = {0};
//创建三个纹理对象
//在纹理资源使用完毕后(一般是程序退出或场景转换时),一定要删除纹理对象,释放资源。
//glDeleteTextures(Count:Integer;TexObj:Pointer);
glGenTextures(3, texts);
//使用glBindTexture将创建的纹理绑定到当前纹理。这样所有的纹理函数都将针对当前纹理。
glBindTexture(GL_TEXTURE_2D, texts[0]);
//设置缩小滤镜
/**
* 第一个参数表明是针对何种纹理进行设置
* 第二个参数表示要设置放大滤镜还是缩小滤镜
*
* 在纹理映射的过程中,如果图元的大小不等于纹理的大小,OpenGL便会对纹理进行缩放以适应图元的尺寸。
* 我们可以通过设置纹理滤镜来决定OpenGL对某个纹理采用的放大、缩小的算法。
*
* 第三个参数表示使用的滤镜
*
* 第三个参数可选项如下:
*
* GL_NEAREST 取最邻近像素
* GL_LINEAR 线性内部插值
* GL_NEAREST_MIPMAP_NEAREST 最近多贴图等级的最邻近像素
* GL_NEAREST_MIPMAP_LINEAR 在最近多贴图等级的内部线性插值
* GL_LINEAR_MIPMAP_NEAREST 在最近多贴图等级的外部线性插值
* GL_LINEAR_MIPMAP_LINEAR 在最近多贴图等级的外部和内部线性插值
*
*/
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
//设置放大滤镜
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//glTexImage2D函数将Pixels数组中的像素值传给当前绑定的纹理对象,于是便创建了纹理,Pixels是最后一个参数
glTexImage2D(
//纹理的类型
GL_TEXTURE_2D,
//纹理的等级 0默认 级的分辨率最大
0,
//gpu内部格式 亮度,灰度图
GL_LUMINANCE,
//纹理图像的宽度和高度 拉升到全屏
width, height,
//边框大小
0,
//像素数据的格式 亮度,灰度图 要与上面一致
GL_LUMINANCE,
//像素值的数据类型
GL_UNSIGNED_BYTE,
//纹理的数据(像素数据)
NULL
);
//使用glBindTexture将创建的纹理绑定到当前纹理。这样所有的纹理函数都将针对当前纹理。
glBindTexture(GL_TEXTURE_2D, texts[1]);
//调用glTexParameter来设置纹理滤镜
//设置缩小滤镜
/**
* 第一个参数表明是针对何种纹理进行设置
* 第二个参数表示要设置放大滤镜还是缩小滤镜
* 第三个参数表示使用的滤镜
*
* 第三个参数可选项如下:
*
* GL_NEAREST 取最邻近像素
* GL_LINEAR 线性内部插值
* GL_NEAREST_MIPMAP_NEAREST 最近多贴图等级的最邻近像素
* GL_NEAREST_MIPMAP_LINEAR 在最近多贴图等级的内部线性插值
* GL_LINEAR_MIPMAP_NEAREST 在最近多贴图等级的外部线性插值
* GL_LINEAR_MIPMAP_LINEAR 在最近多贴图等级的外部和内部线性插值
*
* 多贴图纹理(Mip Mapping)为一个纹理对象生成不同尺寸的图像。在需要时,根据绘制图形的大小来决定采用的纹理
* 等级或者在不同的纹理等级之间进行线性内插。使用多贴图纹理的好处在于消除纹理躁动。这种情况在所绘制的景物
* 离观察者较远时常常发生(如图6.6-1和6.6-2)。由于多贴图纹理现在的渲染速度已经很快,以至于和普通纹理没有
* 什么区别,我们现在一般都使用多贴图纹理。
*/
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
//设置放大滤镜
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//glTexImage2D函数将Pixels数组中的像素值传给当前绑定的纹理对象,于是便创建了纹理,Pixels是最后一个参数
glTexImage2D(
//纹理的类型
GL_TEXTURE_2D,
//纹理的等级 0默认 级的分辨率最大
0,
//gpu内部格式 亮度,灰度图
GL_LUMINANCE,
//纹理图像的宽度和高度 拉升到全屏
width, height,
//边框大小
0,
//像素数据的格式 亮度,灰度图 要与上面一致
GL_LUMINANCE,
//像素值的数据类型
GL_UNSIGNED_BYTE,
//纹理的数据(像素数据)
NULL
);
//使用glBindTexture将创建的纹理绑定到当前纹理。这样所有的纹理函数都将针对当前纹理。
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函数将Pixels数组中的像素值传给当前绑定的纹理对象,于是便创建了纹理,Pixels是最后一个参数
glTexImage2D(
//纹理的类型
GL_TEXTURE_2D,
//纹理的等级 0默认 级的分辨率最大
0,
//gpu内部格式 亮度,灰度图
GL_LUMINANCE,
//纹理图像的宽度和高度 拉升到全屏
width, height,
//边框大小
0,
//像素数据的格式 亮度,灰度图 要与上面一致
GL_LUMINANCE,
//像素值的数据类型
GL_UNSIGNED_BYTE,
//纹理的数据(像素数据)
NULL
);
LOGI("glTexImage2D success");
/**************************纹理设置********************************************/
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];
for (int i = 0; i < 10000; i++) {
//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);
}
//激活第1层纹理,绑定到创建的opengl纹理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texts[0]);
//替换纹理内容
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(eglDisplay, eglSurface);
}
/**************************纹理显示********************************************/
LOGI("eglSwapBuffers success");
env->ReleaseStringUTFChars(url_, url);
}