opengl es 2.0 视频播放

demo: https://github.com/wangzuxing/MyFFmpegH264H265YUVOpenGL

以YUV纹理贴图实现视频播放:
1、把jni端解码的YUV数据/摄像头预览的YUV数据传入glTexImage2D()函数,并以GL_LUMINANCE格式进行纹理加载、绑定
2、把YUV数据产生的相关纹理单元赋给片元着色器的各2D采样器,然后进行纹理采样、数值计算、转换成RGB数据

如下:
GLES20.glTexImage2D
(
GLES20.GL_TEXTURE_2D, //纹理单元的类型
0, //纹理单元的层次,非mipmap纹理level设置为0
GLES20.GL_LUMINANCE,
//纹理单元的数据格式:
// GL_RGB、GL_RGBA、GL_LUMINANCE、GL_LUMINANCE_ALPHA、GL_ALPHA
_video_width, //纹理单元的宽度
_video_height, //纹理单元的高度
0, //纹理单元的边框,如果包含边框取值为1,不包含边框取值为0
GLES20.GL_LUMINANCE, //data所指向的数据的格式
GLES20.GL_UNSIGNED_BYTE, //data所指向的数据的类型
y //指向的数据
);

GLES20Support.java:

public class GLES20Support {
    public static boolean detectOpenGLES20(Context context) {
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 
        ConfigurationInfo info = am.getDeviceConfigurationInfo(); // 获取配置信息, 取得GLES version
        return (info.reqGlEsVersion >= 0x20000);
    }
    ...
}

MainActivity0.java:

protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);

         ...
         if (GLES20Support.detectOpenGLES20(this) == false) { // 判断android系统是否支持opengl es 2.0
              GLES20Support.getNoSupportGLES20Dialog(this);
         }

         GLFrameSurface glSurfaceView = new GLFrameSurface(this);
     glSurfaceView.setEGLContextClientVersion(2);  //设置OpenGL ES 2.0版本支持

     glRenderer = new GLFrameRenderer(null, glSurfaceView, getDM(this));
     glSurfaceView.setRenderer(glRenderer);
         ...

     }

     //1、主线程调用GLFrameRenderer glRenderer
     //2、

     boolean yuv_update = true;
     Handler myHandler = new Handler() {  
        public void handleMessage(Message msg) {   
             switch (msg.what) {   
                  ...
                  case 1:
                      if(yuv_update){
                         glRenderer.update(width, height);
                         yuv_update = false;
                      }
                      byte[] y = new byte[yuvPlanes[0].remaining()];
                    yuvPlanes[0].get(y, 0, y.length);

                    byte[] u = new byte[yuvPlanes[1].remaining()];
                    yuvPlanes[1].get(u, 0, u.length);

                    byte[] v = new byte[yuvPlanes[2].remaining()];
                    yuvPlanes[2].get(v, 0, v.length);

                    glRenderer.update(y, u, v); //GLFrameRenderer glRenderer;
                      break;
                    ...
             }   
             super.handleMessage(msg);   
        }   
    }; 

    // jni端需调用函数,传入解码的yuv数据到java端,由opengl es2.0进行纹理贴图显示
    public void updateYUV(byte[] yuvData, int width0, int height0) {
            synchronized (this) {
              copyFrom(yuvData,width0,height0);
                Message message = new Message();   
            message.what = 1;     
            myHandler.sendMessage(message);
            }
    }

    public ByteBuffer[] yuvPlanes;
    int planeSize;// = width * height;
    int planeSize_l;
    ByteBuffer[] planes = new ByteBuffer[3];   

    //析取出 Y、U、V 分量,送opengl es 2.0 的glTexImage2D()函数产生纹理数据
    //GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, _video_width, _video_height, 0,GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, y); // y u v 
    public void copyFrom(byte[] yuvData, int width, int height) {
          if (yuvPlanes == null) {
                int[] yuvStrides = { width, width / 2, width / 2};
                planeSize = width * height;
                planeSize_l = planeSize*3/2;
            yuvPlanes = new ByteBuffer[3];
            yuvPlanes[0] = ByteBuffer.allocateDirect(yuvStrides[0] * height);
            yuvPlanes[1] = ByteBuffer.allocateDirect(yuvStrides[1] * height / 2);
            yuvPlanes[2] = ByteBuffer.allocateDirect(yuvStrides[2] * height / 2);
          }
        if (yuvData.length < planeSize_l) {
            throw new RuntimeException("Wrong arrays size: " + yuvData.length);
        }
        planes[0] = ByteBuffer.wrap(yuvData, 0, planeSize);
        planes[1] = ByteBuffer.wrap(yuvData, planeSize, planeSize / 4);
        planes[2] = ByteBuffer.wrap(yuvData, planeSize + planeSize / 4, planeSize / 4);

        for (int i = 0; i < 3; i++) {
             yuvPlanes[i].position(0);
             yuvPlanes[i].put(planes[i]);
             yuvPlanes[i].position(0);
             yuvPlanes[i].limit(yuvPlanes[i].capacity());
        }
      }

      /*
    packed formats:将Y、U、V值储存成Macro Pixels阵列,和RGB的存放方式类似。
    planar formats:将Y、U、V的三个份量分别存放在不同的矩阵中。
    COLOR_FormatYUV420Planar:    YUV420P I420
    COLOR_FormatYUV420SemiPlanar:   YUV420SP NV12
    YUV420P,Y,U,V三个分量都是平面格式,分为I420和YV12。I420格式和YV12格式的不同处在U平面和V平面的位置不同。
    在I420格式中,U平面紧跟在Y平面之后,然后才是V平面(即:YUV);但YV12则是相反(即:YVU)。
    YUV420SP, Y分量平面格式,UV打包格式, 即NV12。 NV12与NV21类似,U 和 V 交错排列,不同在于UV顺序。
    I420: YYYYYYYY UU VV    =>YUV420P
    YV12: YYYYYYYY VV UU    =>YUV420P
    NV12: YYYYYYYY UVUV     =>YUV420SP
    NV21: YYYYYYYY VUVU     =>YUV420SP
    */

    //yv12 =》 yuv420p : yvu -> yuv  
    private void swapYV12toI420(byte[] yv12bytes, byte[] i420bytes, int width, int height)   
    {        
        System.arraycopy(yv12bytes, 0, i420bytes, 0,width*height);  
        System.arraycopy(yv12bytes, width*height+width*height/4, i420bytes, width*height,width*height/4);  
        System.arraycopy(yv12bytes, width*height, i420bytes, width*height+width*height/4,width*height/4);    
    }

      // camera preview data --> onFrame() mediacodec编码
      public void onFrame(byte[] buf, int offset, int length, int flag) {   
            swapYV12toI420(buf, h264, width, height); // I420 视频格式

            if(isPlaying){  
                 copyFrom(h264, width ,height);   // h264 存放的就是I420格式的yuv数据
               Message message = new Message(); // ui线程刷新显示
           message.what = 1;     
           myHandler.sendMessage(message);
            }
            ...
      }

}

GLFrameRenderer.java:

public class GLFrameRenderer implements Renderer {

    private ISimplePlayer mParentAct;
    private GLSurfaceView mTargetSurface;
    private GLProgram prog = new GLProgram(0); // 顶点坐标、纹理坐标定义,vertex shader、fragment shader定义、读取,参数的传递、纹理的创建等
    private int mScreenWidth, mScreenHeight;
    private int mVideoWidth, mVideoHeight;
    private ByteBuffer y;
    private ByteBuffer u;
    private ByteBuffer v;

    public GLFrameRenderer(ISimplePlayer callback, GLSurfaceView surface, DisplayMetrics dm) {
        mParentAct = callback;
        mTargetSurface = surface;
        mScreenWidth = 320;//dm.widthPixels;
        mScreenHeight = 240;//dm.heightPixels;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        if (!prog.isProgramBuilt()) {
            prog.buildProgram(); // 2、读取vertex shader、fragment shader, createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
                                 // get handle for "vPosition" and "a_texCoord"
                                 // get uniform location for y/u/v
        }
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        synchronized (this) {
            if (y != null) {
                // reset position, have to be done
                y.position(0);
                u.position(0);
                v.position(0);
                prog.buildTextures(y, u, v, mVideoWidth, mVideoHeight); //3、根据I420的y、u、v数据,产生对应的纹理贴图
                GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 
                GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
                prog.drawFrame(); // 4、GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 渲染\显示纹理贴图
            }
        }
    }

    public void update(int w, int h) {
        Utils.LOGD("update INIT E");
        if (w > 0 && h > 0) {
            if (mScreenWidth > 0 && mScreenHeight > 0) {
                float f1 = 1f * mScreenHeight / mScreenWidth;
                float f2 = 1f * h / w;

                //1、根据视频高、宽比与屏幕高、宽比的比较,调用createBuffers(), 产生vertices buffer,四顶点
                if (f1 == f2) {
                    prog.createBuffers(GLProgram.squareVertices);
                } else if (f1 < f2) {
                    float widScale = f1 / f2;
                    prog.createBuffers(new float[] { -widScale, -1.0f, widScale, -1.0f, -widScale, 1.0f, widScale, 1.0f,});
                } else {
                    float heightScale = f2 / f1;
                    prog.createBuffers(new float[] { -1.0f, -heightScale, 1.0f, -heightScale, -1.0f, heightScale, 1.0f, heightScale, });
                }
            }
            if (w != mVideoWidth && h != mVideoHeight) {
                this.mVideoWidth = w;
                this.mVideoHeight = h;
                int yarraySize = w * h;
                int uvarraySize = yarraySize / 4;
                synchronized (this) {
                    y = ByteBuffer.allocate(yarraySize);
                    u = ByteBuffer.allocate(uvarraySize);
                    v = ByteBuffer.allocate(uvarraySize);
                }
            }
        }
    }

    public void update(byte[] ydata, byte[] udata, byte[] vdata) {
        synchronized (this) {
            y.clear();
            u.clear();
            v.clear();
            y.put(ydata, 0, ydata.length);
            u.put(udata, 0, udata.length);
            v.put(vdata, 0, vdata.length);
            // request to render
            mTargetSurface.requestRender(); //5、GLSurfaceView请求渲染
        }
    }
}

GLProgram.java

public class GLProgram {
    // program id
    private int _program;
    // window position
    public final int mWinPosition;

    // texture id
    private int _textureI;
    private int _textureII;
    private int _textureIII;
    // texture index in gles
    private int _tIindex;
    private int _tIIindex;
    private int _tIIIindex;

    // vertices on screen
    private float[] _vertices;
    // handles
    private int _positionHandle = -1, _coordHandle = -1;
    private int _yhandle = -1, _uhandle = -1, _vhandle = -1;
    private int _ytid = -1, _utid = -1, _vtid = -1;

    // vertices buffer
    private ByteBuffer _vertice_buffer;
    private ByteBuffer _coord_buffer;
    // video width and height
    private int _video_width = -1;
    private int _video_height = -1;
    // flow control
    private boolean isProgBuilt = false;

    public GLProgram(int position) {
        if (position < 0 || position > 4) {
            throw new RuntimeException("Index can only be 0 to 4");
        }
        mWinPosition = position;
        setup(mWinPosition);
    }

    public void setup(int position) {
        switch (mWinPosition) {
        case 1:
            _vertices = squareVertices1; // 
            _textureI = GLES20.GL_TEXTURE0;
            _textureII = GLES20.GL_TEXTURE1;
            _textureIII = GLES20.GL_TEXTURE2;
            _tIindex = 0; // GLES20.GL_TEXTURE0 纹理单元在系统中的索引为0,即opengl es 2.0函数中对参数为GLES20.GL_TEXTURE0的,也可以用0代替
            _tIIindex = 1;
            _tIIIindex = 2;
            break;
        case 2:
            _vertices = squareVertices2;
            _textureI = GLES20.GL_TEXTURE3;
            _textureII = GLES20.GL_TEXTURE4;
            _textureIII = GLES20.GL_TEXTURE5;
            _tIindex = 3;
            _tIIindex = 4;
            _tIIIindex = 5;
            break;
        case 3:
            _vertices = squareVertices3;
            _textureI = GLES20.GL_TEXTURE6;
            _textureII = GLES20.GL_TEXTURE7;
            _textureIII = GLES20.GL_TEXTURE8;
            _tIindex = 6;
            _tIIindex = 7;
            _tIIIindex = 8;
            break;
        case 4:
            _vertices = squareVertices4;
            _textureI = GLES20.GL_TEXTURE9;
            _textureII = GLES20.GL_TEXTURE10;
            _textureIII = GLES20.GL_TEXTURE11;
            _tIindex = 9;
            _tIIindex = 10;
            _tIIIindex = 11;
            break;
        case 0:
        default:
            _vertices = squareVertices;
            _textureI = GLES20.GL_TEXTURE0;
            _textureII = GLES20.GL_TEXTURE1;
            _textureIII = GLES20.GL_TEXTURE2;
            _tIindex = 0;
            _tIIindex = 1;
            _tIIIindex = 2;
            break;
        }
    }

    public boolean isProgramBuilt() {
        return isProgBuilt;
    }

    public void buildProgram() {
        // TODO createBuffers(_vertices, coordVertices);
        if (_program <= 0) {
            _program = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
        }
        Utils.LOGD("_program = " + _program);

        //get handle for "vPosition" and "a_texCoord"
        //获取程序中顶点坐标属性引用(句柄)
        _positionHandle = GLES20.glGetAttribLocation(_program, "vPosition");
        Utils.LOGD("_positionHandle = " + _positionHandle);
        checkGlError("glGetAttribLocation vPosition");
        if (_positionHandle == -1) {
            throw new RuntimeException("Could not get attribute location for vPosition");
        }
        //获取程序中顶点纹理坐标属性引用(句柄)
        _coordHandle = GLES20.glGetAttribLocation(_program, "a_texCoord");
        Utils.LOGD("_coordHandle = " + _coordHandle);
        checkGlError("glGetAttribLocation a_texCoord");
        if (_coordHandle == -1) {
            throw new RuntimeException("Could not get attribute location for a_texCoord");
        }

        // get uniform location for y/u/v, we pass data through these uniforms
        //获取程序中2D采样器引用(句柄),纹理贴图
        _yhandle = GLES20.glGetUniformLocation(_program, "tex_y"); // 
        Utils.LOGD("_yhandle = " + _yhandle);
        checkGlError("glGetUniformLocation tex_y");
        if (_yhandle == -1) {
            throw new RuntimeException("Could not get uniform location for tex_y");
        }

        _uhandle = GLES20.glGetUniformLocation(_program, "tex_u");
        Utils.LOGD("_uhandle = " + _uhandle);
        checkGlError("glGetUniformLocation tex_u");
        if (_uhandle == -1) {
            throw new RuntimeException("Could not get uniform location for tex_u");
        }

        _vhandle = GLES20.glGetUniformLocation(_program, "tex_v");
        Utils.LOGD("_vhandle = " + _vhandle);
        checkGlError("glGetUniformLocation tex_v");
        if (_vhandle == -1) {
            throw new RuntimeException("Could not get uniform location for tex_v");
        }

        isProgBuilt = true;
    }

    //build a set of textures, one for R, one for G, and one for B.
    public void buildTextures(Buffer y, Buffer u, Buffer v, int width, int height) {
        boolean videoSizeChanged = (width != _video_width || height != _video_height);
        if (videoSizeChanged) {
            _video_width = width;
            _video_height = height;
            Utils.LOGD("buildTextures videoSizeChanged: w=" + _video_width + " h=" + _video_height);
        }

        // building texture for Y data
        if (_ytid < 0 || videoSizeChanged) {
            if (_ytid >= 0) {
                Utils.LOGD("glDeleteTextures Y");
                GLES20.glDeleteTextures(1, new int[] { _ytid }, 0);
                checkGlError("glDeleteTextures");
            }
            // GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
            //生成纹理ID
                int[] textures = new int[1];
                GLES20.glGenTextures
                (
                        1,          //产生的纹理id的数量
                        textures,   //纹理id的数组
                         0          //偏移量
                );   
            checkGlError("glGenTextures");
            _ytid = textures[0];
            Utils.LOGD("glGenTextures Y = " + _ytid);
        }
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, _ytid);
        checkGlError("glBindTexture");
        //实际加载纹理
        GLES20.glTexImage2D
        (
           GLES20.GL_TEXTURE_2D,  //纹理单元的类型
           0,                     //纹理单元的层次,非mipmap纹理level设置为0
           GLES20.GL_LUMINANCE,   //纹理单元的数据格式 GL_RGB、GL_RGBA、GL_LUMINANCE、GL_LUMINANCE_ALPHA、GL_ALPHA
           _video_width,          //纹理单元的宽度
           _video_height,         //纹理单元的高度
           0,                     //纹理单元的边框,如果包含边框取值为1,不包含边框取值为0
           GLES20.GL_LUMINANCE,   //data所指向的数据的格式
           GLES20.GL_UNSIGNED_BYTE, //data所指向的数据的类型
           y                        //指向的数据
        );               
        checkGlError("glTexImage2D");

        //非Mipmap纹理采样过滤参数   
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

        //ST方向纹理拉伸方式
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

        // building texture for U data
        if (_utid < 0 || videoSizeChanged) {
            if (_utid >= 0) {
                Utils.LOGD("glDeleteTextures U");
                GLES20.glDeleteTextures(1, new int[] { _utid }, 0);
                checkGlError("glDeleteTextures");
            }
            int[] textures = new int[1];
            GLES20.glGenTextures(1, textures, 0);
            checkGlError("glGenTextures");
            _utid = textures[0];
            Utils.LOGD("glGenTextures U = " + _utid);
        }
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, _utid);
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, _video_width / 2, _video_height / 2, 0,
                GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, u);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

        // building texture for V data
        if (_vtid < 0 || videoSizeChanged) {
            if (_vtid >= 0) {
                Utils.LOGD("glDeleteTextures V");
                GLES20.glDeleteTextures(1, new int[] { _vtid }, 0);
                checkGlError("glDeleteTextures");
            }
            int[] textures = new int[1];
            GLES20.glGenTextures(1, textures, 0);
            checkGlError("glGenTextures");
            _vtid = textures[0];
            Utils.LOGD("glGenTextures V = " + _vtid);
        }
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, _vtid);
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, _video_width / 2, _video_height / 2, 0,
                GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, v);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
    }

    //render the frame the YUV data will be converted to RGB by shader.
    public void drawFrame() {
        GLES20.glUseProgram(_program);
        checkGlError("glUseProgram");

        //指定顶点属性数组
        GLES20.glVertexAttribPointer(_positionHandle, 2, GLES20.GL_FLOAT, false, 8, _vertice_buffer);  
        //指定渲染时索引值为_positionHandle的顶点属性数组的数据格式和位置, 顶点数据和shader程序中的变量进行关联
        checkGlError("glVertexAttribPointer mPositionHandle");
        GLES20.glEnableVertexAttribArray(_positionHandle); //使能索引值为_positionHandle的顶点属性数组

        GLES20.glVertexAttribPointer(_coordHandle, 2, GLES20.GL_FLOAT, false, 8, _coord_buffer);
        checkGlError("glVertexAttribPointer maTextureHandle");
        GLES20.glEnableVertexAttribArray(_coordHandle);

        // bind textures
        GLES20.glActiveTexture(_textureI);                 //激活纹理单元
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, _ytid); //绑定纹理对象到纹理单元
        GLES20.glUniform1i(_yhandle, _tIindex);            //通过_yhandle句柄,把该纹理单元赋值给shader中sampler2D tex_y
        //GLES20.GL_TEXTURE0~GLES20.GL_TEXTURE31 纹理单元在系统中的索引为0~31,即opengl es 2.0函数中对参数为GLES20.GL_TEXTUREi,也可以用i代替(i=0~31)

        GLES20.glActiveTexture(_textureII);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, _utid);
        GLES20.glUniform1i(_uhandle, _tIIindex);

        GLES20.glActiveTexture(_textureIII);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, _vtid);
        GLES20.glUniform1i(_vhandle, _tIIIindex);

        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); // GL_TRIANGLE_STRIP绘制方式,数组中顶点的数量4
        GLES20.glFinish();

        GLES20.glDisableVertexAttribArray(_positionHandle);  //禁用索引值为_positionHandle的顶点属性数组
        GLES20.glDisableVertexAttribArray(_coordHandle);
    }

    /**
     * create program and load shaders, fragment shader is very important.
     */
    public int createProgram(String vertexSource, String fragmentSource) {
        // create shaders
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);    //加载顶点着色器
        int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);  //加载片元着色器
        // just check
        Utils.LOGD("vertexShader = " + vertexShader);
        Utils.LOGD("pixelShader = " + pixelShader);

        int program = GLES20.glCreateProgram(); //创建程序 
        if (program != 0) {
            GLES20.glAttachShader(program, vertexShader); //向程序中加入顶点着色器
            checkGlError("glAttachShader");
            GLES20.glAttachShader(program, pixelShader);  //向程序中加入片元着色器
            checkGlError("glAttachShader");
            GLES20.glLinkProgram(program); //链接程序
            int[] linkStatus = new int[1]; //存放链接成功program数量的数组
            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); //获取program的链接情况
            if (linkStatus[0] != GLES20.GL_TRUE) {
                Utils.LOGE("Could not link program: ");
                Utils.LOGE(GLES20.glGetProgramInfoLog(program));
                GLES20.glDeleteProgram(program);
                program = 0;
            }
        }
        return program;
    }

    /**
     * create shader with given source.
     */
    private int loadShader(int shaderType, String source) {
        int shader = GLES20.glCreateShader(shaderType); //根据类型创建shader
        if (shader != 0) {
            GLES20.glShaderSource(shader, source); //加载shader的源代码
            GLES20.glCompileShader(shader); //编译shader
            int[] compiled = new int[1]; //存放编译成功shader数量的数组
            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); //获取Shader的编译情况
            if (compiled[0] == 0) {
                Utils.LOGE("Could not compile shader " + shaderType);
                Utils.LOGE(GLES20.glGetShaderInfoLog(shader));
                GLES20.glDeleteShader(shader);
                shader = 0;
            }
        }
        return shader;
    }

    /**
     * these two buffers are used for holding vertices, screen vertices and texture vertices.
     */
    void createBuffers(float[] vert) {
        _vertice_buffer = ByteBuffer.allocateDirect(vert.length * 4); // 创建顶点坐标数据缓冲
        _vertice_buffer.order(ByteOrder.nativeOrder());
        _vertice_buffer.asFloatBuffer().put(vert);
        _vertice_buffer.position(0);

        if (_coord_buffer == null) {
            _coord_buffer = ByteBuffer.allocateDirect(coordVertices.length * 4); // 根据纹理坐标数组---创建顶点纹理坐标数据缓冲
            _coord_buffer.order(ByteOrder.nativeOrder()); //设置本地字节序
            _coord_buffer.asFloatBuffer().put(coordVertices); //转换为Float型缓冲,并向缓冲区中放入顶点纹理数据
            _coord_buffer.position(0); //设置缓冲区起始位置
        }
    }

    private void checkGlError(String op) {
        int error;
        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
            Utils.LOGE("***** " + op + ": glError " + error);
            throw new RuntimeException(op + ": glError " + error);
        }
    }
    //默认定义的屏幕顶点坐标
    static float[] squareVertices = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, }; // fullscreen
    static float[] squareVertices1 = { -1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, }; // left-top
    static float[] squareVertices2 = { 0.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, }; // right-bottom
    static float[] squareVertices3 = { -1.0f, -1.0f, 0.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, }; // left-bottom
    static float[] squareVertices4 = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, }; // right-top
    //顶点纹理坐标
    private static float[] coordVertices = { 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, };// whole-texture

    //顶点着色器代码 vec4 vPosition齐次坐标(x,y,0,1)
    private static final String VERTEX_SHADER = "attribute vec4 vPosition;\n" + "attribute vec2 a_texCoord;\n"
            + "varying vec2 tc;\n" + "void main() {\n" + "gl_Position = vPosition;\n" + "tc = a_texCoord;\n" + "}\n";

    //片元着色器代码, sampler2D对应GL_TEXTURE_2D的纹理采样器
    private static final String FRAGMENT_SHADER = "precision mediump float;\n" + "uniform sampler2D tex_y;\n"
            + "uniform sampler2D tex_u;\n" + "uniform sampler2D tex_v;\n" + "varying vec2 tc;\n" + "void main() {\n"
            + "vec4 c = vec4((texture2D(tex_y, tc).r - 16./255.) * 1.164);\n"
            + "vec4 U = vec4(texture2D(tex_u, tc).r - 128./255.);\n"
            + "vec4 V = vec4(texture2D(tex_v, tc).r - 128./255.);\n" + "c += V * vec4(1.596, -0.813, 0, 0);\n"
            + "c += U * vec4(0, -0.392, 2.017, 0);\n" + "c.a = 1.0;\n" + "gl_FragColor = c;\n" + "}\n";

}