EGL和OpenGLESGLES显示YUV视频
1.EGL
EGL是OpenGL ES与系统原始窗口的适配层:
Display:用于与原生窗口建立连接。 Surface:用于渲染的区域。 Context:创建渲染上下文。指的是OpenGL ES项目运行需要的所有数据结构。如:定点,着色器,顶点数据矩阵。
2.GLSL
顶点着色器针对每个顶点执行一次,用于确定顶点的位置。
片元着色器针对每个片元(像素)执行一次,用于确定片元(像素)的颜色。
3.Android向底层传递Surface句柄
public class XPlay extends GLSurfaceView implements Runnable,SurfaceHolder.Callback
{
public XPlay(Context context, AttributeSet attrs) {
super( context, attrs );
}
@Override
public void run() {
Open("/sdcard/outyuv",getHolder().getSurface());
}
@Override
public void surfaceCreated(SurfaceHolder var1)
{
new Thread( this ).start();
}
@Override
public void surfaceChanged(SurfaceHolder var1, int var2, int var3, int var4)
{
}
@Override
public void surfaceDestroyed(SurfaceHolder var1)
{
}
public native void Open(String url,Object surface);
}
复制代码
4.获取原始窗口
#include <android/native_window_jni.h>
复制代码
extern "C"
JNIEXPORT void JNICALL
Java_aplay_testopengles_XPlay_Open(JNIEnv *env, jobject instance, jstring url_, jobject surface) {
//1 获取原始窗口
ANativeWindow *nwin = ANativeWindow_fromSurface(env,surface);
}
复制代码
ANativeWindow_fromSurface():返回与Java Surface对象关联的ANativeWindow,通过本地代码与之交互。这获得了返回的ANativeWindow的引用; 使用结束之后要使用ANativeWindow_release(),这样它才不会泄漏。
ANativeWindow * ANativeWindow_fromSurface(
JNIEnv *env,
jobject surface
)
复制代码
ANativeWindow:不透明类型,提供对本地窗口的访问。
struct ANativeWindow
{
#ifdef __cplusplus
ANativeWindow()
: flags(0), minSwapInterval(0), maxSwapInterval(0), xdpi(0), ydpi(0)
{
common.magic = ANDROID_NATIVE_WINDOW_MAGIC;
common.version = sizeof(ANativeWindow);
memset(common.reserved, 0, sizeof(common.reserved));
}
/* Implement the methods that sp<ANativeWindow> expects so that it
can be used to automatically refcount ANativeWindow's. */
void incStrong(const void* id) const {
common.incRef(const_cast<android_native_base_t*>(&common));
}
void decStrong(const void* id) const {
common.decRef(const_cast<android_native_base_t*>(&common));
}
#endif
struct android_native_base_t common;
/* flags describing some attributes of this surface or its updater */
const uint32_t flags;
/* min swap interval supported by this updated */
const int minSwapInterval;
/* max swap interval supported by this updated */
const int maxSwapInterval;
/* horizontal and vertical resolution in DPI */
const float xdpi;
const float ydpi;
/* Some storage reserved for the OEM's driver. */
intptr_t oem[4];
/*
* Set the swap interval for this surface.
*
* Returns 0 on success or -errno on error.
*/
int (*setSwapInterval)(struct ANativeWindow* window,
int interval);
/*
* Hook called by EGL to acquire a buffer. After this call, the buffer
* is not locked, so its content cannot be modified. This call may block if
* no buffers are available.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* Returns 0 on success or -errno on error.
*/
int (*dequeueBuffer)(struct ANativeWindow* window,
struct ANativeWindowBuffer** buffer);
/*
* hook called by EGL to lock a buffer. This MUST be called before modifying
* the content of a buffer. The buffer must have been acquired with
* dequeueBuffer first.
*
* Returns 0 on success or -errno on error.
*/
int (*lockBuffer)(struct ANativeWindow* window,
struct ANativeWindowBuffer* buffer);
/*
* Hook called by EGL when modifications to the render buffer are done.
* This unlocks and post the buffer.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* Buffers MUST be queued in the same order than they were dequeued.
*
* Returns 0 on success or -errno on error.
*/
int (*queueBuffer)(struct ANativeWindow* window,
struct ANativeWindowBuffer* buffer);
/*
* hook used to retrieve information about the native window.
*
* Returns 0 on success or -errno on error.
*/
int (*query)(const struct ANativeWindow* window,
int what, int* value);
/*
* hook used to perform various operations on the surface.
* (*perform)() is a generic mechanism to add functionality to
* ANativeWindow while keeping backward binary compatibility.
*
* DO NOT CALL THIS HOOK DIRECTLY. Instead, use the helper functions
* defined below.
*
* (*perform)() returns -ENOENT if the 'what' parameter is not supported
* by the surface's implementation.
*
* The valid operations are:
* NATIVE_WINDOW_SET_USAGE
* NATIVE_WINDOW_CONNECT (deprecated)
* NATIVE_WINDOW_DISCONNECT (deprecated)
* NATIVE_WINDOW_SET_CROP (private)
* NATIVE_WINDOW_SET_BUFFER_COUNT
* NATIVE_WINDOW_SET_BUFFERS_GEOMETRY (deprecated)
* NATIVE_WINDOW_SET_BUFFERS_TRANSFORM
* NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP
* NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS
* NATIVE_WINDOW_SET_BUFFERS_FORMAT
* NATIVE_WINDOW_SET_SCALING_MODE (private)
* NATIVE_WINDOW_LOCK (private)
* NATIVE_WINDOW_UNLOCK_AND_POST (private)
* NATIVE_WINDOW_API_CONNECT (private)
* NATIVE_WINDOW_API_DISCONNECT (private)
* NATIVE_WINDOW_SET_BUFFERS_USER_DIMENSIONS (private)
* NATIVE_WINDOW_SET_POST_TRANSFORM_CROP (private)
*
*/
int (*perform)(struct ANativeWindow* window,
int operation, ... );
/*
* Hook used to cancel a buffer that has been dequeued.
* No synchronization is performed between dequeue() and cancel(), so
* either external synchronization is needed, or these functions must be
* called from the same thread.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*/
int (*cancelBuffer)(struct ANativeWindow* window,
struct ANativeWindowBuffer* buffer);
void* reserved_proc[2];
};
复制代码
5.初始化EGL
#include <EGL/egl.h>
复制代码
///EGL
//1 EGL display创建和初始化
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if(display == EGL_NO_DISPLAY)
{
LOGD("eglGetDisplay failed!");
return;
}
if(EGL_TRUE != eglInitialize(display,0,0))
{
LOGD("eglInitialize failed!");
return;
}
//2 surface
//2-1 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(display,configSpec,&config,1,&configNum))
{
LOGD("eglChooseConfig failed!");
return;
}
//创建surface
EGLSurface winsurface = eglCreateWindowSurface(display,config,nwin,0);
if(winsurface == EGL_NO_SURFACE)
{
LOGD("eglCreateWindowSurface failed!");
return;
}
//3 context 创建关联的上下文
const EGLint ctxAttr[] = {
EGL_CONTEXT_CLIENT_VERSION,2,EGL_NONE
};
EGLContext context = eglCreateContext(display,config,EGL_NO_CONTEXT,ctxAttr);
if(context == EGL_NO_CONTEXT)
{
LOGD("eglCreateContext failed!");
return;
}
if(EGL_TRUE != eglMakeCurrent(display,winsurface,winsurface,context))
{
LOGD("eglMakeCurrent failed!");
return;
}
LOGD("EGL Init Success!");
复制代码
EGLDisplay eglGetDisplay(EGLNativeDisplayType display_id):返回需要显示内容的对象的句柄。
EGLDisplay:EGL把这些不同平台的显示系统抽象为一个独立的类型:EGLDisplay。
eglInitialize():初始化EGLDisplay。
EGLBoolean eglInitialize(EGLDisplay display, EGLint* majorVersion, EGLint* minorVersion);
复制代码
EGLConfig:初始化EGLDisplay过后,要选择一个合适的“绘图表面”。
EGLBoolean eglChooseConfig(EGLDisplay display,
const EGLint* attribs, // 你想要的属性事先定义到这个数组里
EGLConfig* configs, // 图形系统将返回若干满足条件的配置到该数组
EGLint maxConfigs, // 上面数组的容量
EGLint* numConfigs); // 图形系统返回的可用的配置个数
复制代码
eglCreateWindowSurface():为创建好的EGLDisplay创建窗口。
EGLSurface eglCreateWindowSurface(EGLDisplay display,
EGLConfig config,
EGLNativeWindowType window, // 在Windows上就是HWND类型
const EGLint* attribs); // 此属性表非彼属性表
复制代码
EGLSurface:EGLSurface 可以是由 EGL 分配的离屏缓冲区(称为“pbuffer”),或由操作系统分配的窗口。EGL 窗口 Surface 通过 eglCreateWindowSurface() 调用被创建。该调用将“窗口对象”作为参数,在 Android 上,该对象可以是 SurfaceView、SurfaceTexture、SurfaceHolder 或 Surface,所有这些对象下面都有一个 BufferQueue。当您进行此调用时,EGL 将创建一个新的 EGLSurface 对象,并将其连接到窗口对象的 BufferQueue 的生产方接口。此后,渲染到该 EGLSurface 会导致一个缓冲区离开队列、进行渲染,然后排队等待消耗方使用。
eglCreateContext():eglCreateContext为当前渲染API(使用eglBindAPI设置)创建EGL渲染context,并返回context的句柄。 context然后可以用于渲染到EGL绘图表面。 如果eglCreateContext无法创建渲染context,则返回EGL_NO_CONTEXT。
EGLContext eglCreateContext(EGLDisplay display,
EGLConfig config,
EGLContext share_context,
EGLint const * attrib_list);
复制代码
display:指定EGL显示连接。
config:指定定义可用于渲染context的帧缓冲区资源的EGL帧缓冲区配置。
share_context:指定用于共享数据的另一个EGL渲染context,由与context相对应的客户端API定义。 数据也与share_context共享数据的所有其他context共享。 EGL_NO_CONTEXT表示不会发生共享。
attrib_list:为正在创建的context指定属性和属性值。 只能指定属性EGL_CONTEXT_CLIENT_VERSION。
EGLContext:OpenGL ES 图形上下文,它代表了OpenGL状态机;如果没有它,OpenGL指令就没有执行的环境。
eglMakeCurrent():eglMakeCurrent将context绑定到当前渲染线程以及绘制和读取表面。 绘制用于所有GL操作,除了读取的任何像素数据(glReadPixels,glCopyTexImage2D和glCopyTexSubImage2D),它取自读取的帧缓冲区值。
如果调用线程已经有当前的渲染context,那么该context将被刷新并标记为不再是最新的。
当context首次生成时,viewport和scissor尺寸被设置为绘制表面的大小。 当context随后变为当前时,viewport和scissor不会被修改。
EGLBoolean eglMakeCurrent( EGLDisplay display,
EGLSurface draw,
EGLSurface read,
EGLContext context);
复制代码
6.顶点和片元shader初始化
#include <GLES2/gl2.h>
复制代码
顶点着色器
片元着色器
//顶点和片元shader初始化
//顶点shader初始化
GLint vsh = InitShader(vertexShader,GL_VERTEX_SHADER);
//片元yuv420 shader初始化
GLint fsh = InitShader(fragYUV420P,GL_FRAGMENT_SHADER);
复制代码
//顶点着色器glsl
#define GET_STR(x) #x
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;
}
);
//片元着色器,软解码和部分x86硬解码
static const char *fragYUV420P = GET_STR(
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;
yuv.b = texture2D(vTexture,vTexCoord).r - 0.5;
rgb = mat3(1.0, 1.0, 1.0,
0.0,-0.39465,2.03211,
1.13983,-0.58060,0.0)*yuv;//指定的yuv转换为rgb的公式
//输出像素颜色
gl_FragColor = vec4(rgb,1.0);
}
);
GLint InitShader(const char *code,GLint type)
{
//创建shader
GLint sh = glCreateShader(type);
if(sh == 0)
{
LOGD("glCreateShader %d failed!",type);
return 0;
}
//加载shader
glShaderSource(sh,
1, //shader数量
&code, //shader代码
0); //代码长度
//编译shader
glCompileShader(sh);
//获取编译情况
GLint status;
glGetShaderiv(sh,GL_COMPILE_STATUS,&status);
if(status == 0)
{
LOGD("glCompileShader failed!");
return 0;
}
LOGD("glCompileShader success!");
return sh;
}
复制代码
7.创建渲染程序
//创建渲染程序
GLint program = glCreateProgram();
if(program == 0)
{
LOGD("glCreateProgram failed!");
return;
}
//渲染程序中加入着色器代码
glAttachShader(program,vsh);
glAttachShader(program,fsh);
//链接程序
glLinkProgram(program);
GLint status = 0;
glGetProgramiv(program,GL_LINK_STATUS,&status);
if(status != GL_TRUE)
{
LOGD("glLinkProgram failed!");
return;
}
glUseProgram(program);
LOGD("glLinkProgram success!");
复制代码
8.传递顶点数据
//加入三维顶点数据 两个三角形组成正方形
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);
//传递顶点
glVertexAttribPointer(apos,3,GL_FLOAT,GL_FALSE,12,vers);
复制代码
9.传递材质数据
//加入材质坐标数据
static float txts[] = {
1.0f,0.0f , //右下
0.0f,0.0f,
1.0f,1.0f,
0.0f,1.0f
};
GLuint atex = (GLuint)glGetAttribLocation(program,"aTexCoord");
glEnableVertexAttribArray(atex);
glVertexAttribPointer(atex,2,GL_FLOAT,GL_FALSE,8,txts);
复制代码
10.材质纹理初始化
int width = 424;
int height = 240;
//材质纹理初始化
//设置纹理层
glUniform1i( glGetUniformLocation(program,"yTexture"),0); //对于纹理第1层
glUniform1i( glGetUniformLocation(program,"uTexture"),1); //对于纹理第2层
glUniform1i( glGetUniformLocation(program,"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);
//设置纹理的格式和大小
glTexImage2D(GL_TEXTURE_2D,
0, //细节基本 0默认
GL_LUMINANCE,//gpu内部格式 亮度,灰度图
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, //拉升到全屏
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 //纹理的数据
);
复制代码
11.纹理显示
纹理的修改和显示
FILE *fp = fopen(url,"rb");
if(!fp)
{
LOGD("open file %s failed!",url);
return;
}
unsigned char *buf[3] = {0};//将显示的材质存放到buf中
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++)
{
//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(display,winsurface);
}
复制代码