package com.example.datalibrary.deptrum.openglhelper; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.pm.ConfigurationInfo; import android.opengl.GLES20; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; /** * */ public class YUVRendThread extends BaseOpenglRenderThread { private GLFrameRenderer glFrameRenderer; public YUVRendThread(String name, SurfaceView mSurfaceView) { super(name, mSurfaceView); tAG = YUVRendThread.class.getSimpleName(); glFrameRenderer = new GLFrameRenderer(); mSurfaceView.getHolder().addCallback(glFrameRenderer); glFrameRenderer.setResolution(640, 480); glFrameRenderer.setDisplayOrientation(0); glFrameRenderer.displayMirror(false); glFrameRenderer.creatBuffer(640, 480); } @Override protected void creatProgram() { if (glFrameRenderer != null) { glFrameRenderer.creatProgram(); } } @Override protected void draw() { if (glFrameRenderer != null) { glFrameRenderer.onDrawFrame(); } } public void update(byte[] yuv420p) { if (glFrameRenderer != null) { glFrameRenderer.update(yuv420p); notifyDraw(); } } public static class GLProgram { private static final String VERTEX_SHADER = "uniform mat4 uMVPMatrix;\n" + "attribute vec4 vPosition;\n" + "attribute vec2 a_texCoord;\n" + "varying vec2 tc;\n" + "void main() {\n" + "gl_Position = uMVPMatrix * vPosition;\n" + "tc = a_texCoord;\n" + "}\n"; 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"; private float[] mViewMatrix = new float[16]; private int mVPMatrixHandle = -1; static float[] s0Matrix = { 1.0f, 0.0f, 0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; static float[] s0MirrorMatrix = { -1.0f, 0.0f, 0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; static float[] s90Matrix = { 0.0f, -1.0f, 0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; static float[] s90MirrorMatrix = { 0.0f, -1.0f, 0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; static float[] s180Matrix = { -1.0f, 0.0f, 0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; static float[] s180MirrorMatrix = { 1.0f, 0.0f, 0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; static float[] s270Matrix = { 0.0f, 1.0f, 0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; static float[] s270MirrorMatrix = { 0.0f, 1.0f, 0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; 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*/ public final int mWinPosition; private int mGLProgram; private int mGLTextureI; private int mGLTextureII; private int mGLTextureIII; private int mGLIndexI; private int mGLIndexII; private int mGLTIndexIII; private float[] mGLVertices; private int mPositionHandle = -1; private int mCoordHandle = -1; private int mYhandle = -1; private int mUhandle = -1; private int mVhandle = -1; private int mYtid = -1; private int mUtid = -1; private int mVtid = -1; private ByteBuffer mVerticeBuffer; private ByteBuffer mCoordBuffer; private int mVideoWidth = -1; private int mVideoHeight = -1; private boolean isProgBuilt = false; /** * position can only be 0~4:
* fullscreen => 0
* left-top => 1
* right-top => 2
* left-bottom => 3
* right-bottom => 4 */ public GLProgram(int position) { if (position < 0 || position > 4) { throw new RuntimeException("Index can only be 0 to 4"); } mWinPosition = position; setup(); } /** * prepared for later use */ private void setup() { switch (mWinPosition) { case 1: mGLVertices = squareVertices1; mGLTextureI = GLES20.GL_TEXTURE0; mGLTextureII = GLES20.GL_TEXTURE1; mGLTextureIII = GLES20.GL_TEXTURE2; mGLIndexI = 0; mGLIndexII = 1; mGLTIndexIII = 2; break; case 2: mGLVertices = squareVertices2; mGLTextureI = GLES20.GL_TEXTURE3; mGLTextureII = GLES20.GL_TEXTURE4; mGLTextureIII = GLES20.GL_TEXTURE5; mGLIndexI = 3; mGLIndexII = 4; mGLTIndexIII = 5; break; case 3: mGLVertices = squareVertices3; mGLTextureI = GLES20.GL_TEXTURE6; mGLTextureII = GLES20.GL_TEXTURE7; mGLTextureIII = GLES20.GL_TEXTURE8; mGLIndexI = 6; mGLIndexII = 7; mGLTIndexIII = 8; break; case 4: mGLVertices = squareVertices4; mGLTextureI = GLES20.GL_TEXTURE9; mGLTextureII = GLES20.GL_TEXTURE10; mGLTextureIII = GLES20.GL_TEXTURE11; mGLIndexI = 9; mGLIndexII = 10; mGLTIndexIII = 11; break; case 0: default: mGLVertices = squareVertices; mGLTextureI = GLES20.GL_TEXTURE0; mGLTextureII = GLES20.GL_TEXTURE1; mGLTextureIII = GLES20.GL_TEXTURE2; mGLIndexI = 0; mGLIndexII = 1; mGLTIndexIII = 2; break; } } public boolean isProgramBuilt() { return isProgBuilt; } public void buildProgram() { if (mGLProgram <= 0) { mGLProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER); } /* * get handle for "vPosition" and "a_texCoord" */ try { mPositionHandle = GLES20.glGetAttribLocation(mGLProgram, "vPosition"); checkGlError("glGetAttribLocation vPosition"); if (mPositionHandle == -1) { throw new RuntimeException("Could not get attribute location for vPosition"); } mCoordHandle = GLES20.glGetAttribLocation(mGLProgram, "a_texCoord"); checkGlError("glGetAttribLocation a_texCoord"); if (mCoordHandle == -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 */ mYhandle = GLES20.glGetUniformLocation(mGLProgram, "tex_y"); checkGlError("glGetUniformLocation tex_y"); if (mYhandle == -1) { throw new RuntimeException("Could not get uniform location for tex_y"); } mUhandle = GLES20.glGetUniformLocation(mGLProgram, "tex_u"); checkGlError("glGetUniformLocation tex_u"); if (mUhandle == -1) { throw new RuntimeException("Could not get uniform location for tex_u"); } mVhandle = GLES20.glGetUniformLocation(mGLProgram, "tex_v"); checkGlError("glGetUniformLocation tex_v"); if (mVhandle == -1) { throw new RuntimeException("Could not get uniform location for tex_v"); } mVPMatrixHandle = GLES20.glGetUniformLocation(mGLProgram, "uMVPMatrix"); isProgBuilt = true; } catch (RuntimeException e) { e.printStackTrace(); } } public void releaseProgram() { GLES20.glUseProgram(0); if (mGLProgram >= 0) { GLES20.glDeleteProgram(mGLProgram); } mGLProgram = -1; isProgBuilt = false; } /** * 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 != mVideoWidth || height != mVideoHeight); if (videoSizeChanged) { mVideoWidth = width; mVideoHeight = height; } // building texture for Y data if (mYtid < 0 || videoSizeChanged) { if (mYtid >= 0) { GLES20.glDeleteTextures(1, new int[]{mYtid}, 0); checkGlError("glDeleteTextures"); } // GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); int[] textures = new int[1]; GLES20.glGenTextures(1, textures, 0); checkGlError("glGenTextures"); mYtid = textures[0]; } GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mYtid); checkGlError("glBindTexture"); GLES20.glTexImage2D( GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, mVideoWidth, mVideoHeight, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, y); checkGlError("glTexImage2D"); 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 U data if (mUtid < 0 || videoSizeChanged) { if (mUtid >= 0) { GLES20.glDeleteTextures(1, new int[]{mUtid}, 0); checkGlError("glDeleteTextures"); } int[] textures = new int[1]; GLES20.glGenTextures(1, textures, 0); checkGlError("glGenTextures"); mUtid = textures[0]; } GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mUtid); GLES20.glTexImage2D( GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, mVideoWidth / 2, mVideoHeight / 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 (mVtid < 0 || videoSizeChanged) { if (mVtid >= 0) { GLES20.glDeleteTextures(1, new int[]{mVtid}, 0); checkGlError("glDeleteTextures"); } int[] textures = new int[1]; GLES20.glGenTextures(1, textures, 0); checkGlError("glGenTextures"); mVtid = textures[0]; } GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mVtid); GLES20.glTexImage2D( GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, mVideoWidth / 2, mVideoHeight / 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); } public void setDisplayOrientation(int degrees, boolean needMirror) { if (degrees == 0) { if (needMirror) { mViewMatrix = s0MirrorMatrix; } else { mViewMatrix = s0Matrix; } } else if (degrees == 90) { if (needMirror) { mViewMatrix = s90MirrorMatrix; } else { mViewMatrix = s90Matrix; } } else if (degrees == 180) { if (needMirror) { mViewMatrix = s180MirrorMatrix; } else { mViewMatrix = s180Matrix; } } else if (degrees == 270) { if (needMirror) { mViewMatrix = s270MirrorMatrix; } else { mViewMatrix = s270Matrix; } } else { } } /** * render the frame the YUV data will be converted to RGB by shader. */ public void drawFrame() { if (null == mVerticeBuffer){ return; } GLES20.glUseProgram(mGLProgram); checkGlError("glUseProgram"); GLES20.glUniformMatrix4fv(mVPMatrixHandle, 1, false, mViewMatrix, 0); GLES20.glVertexAttribPointer(mPositionHandle, 2, GLES20.GL_FLOAT, false, 8, mVerticeBuffer); checkGlError("glVertexAttribPointer mPositionHandle"); GLES20.glEnableVertexAttribArray(mPositionHandle); GLES20.glVertexAttribPointer(mCoordHandle, 2, GLES20.GL_FLOAT, false, 8, mCoordBuffer); checkGlError("glVertexAttribPointer maTextureHandle"); GLES20.glEnableVertexAttribArray(mCoordHandle); // bind textures GLES20.glActiveTexture(mGLTextureI); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mYtid); GLES20.glUniform1i(mYhandle, mGLIndexI); GLES20.glActiveTexture(mGLTextureII); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mUtid); GLES20.glUniform1i(mUhandle, mGLIndexII); GLES20.glActiveTexture(mGLTextureIII); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mVtid); GLES20.glUniform1i(mVhandle, mGLTIndexIII); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); GLES20.glFinish(); GLES20.glDisableVertexAttribArray(mPositionHandle); GLES20.glDisableVertexAttribArray(mCoordHandle); } /** * 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 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]; GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); if (linkStatus[0] != GLES20.GL_TRUE) { GLES20.glDeleteProgram(program); program = 0; } } return program; } /** * create shader with given source. */ private int loadShader(int shaderType, String source) { int shader = GLES20.glCreateShader(shaderType); if (shader != 0) { GLES20.glShaderSource(shader, source); GLES20.glCompileShader(shader); int[] compiled = new int[1]; GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); if (compiled[0] == 0) { GLES20.glDeleteShader(shader); shader = 0; } } return shader; } /** * these two buffers are used for holding vertices, screen vertices and texture vertices. */ void createBuffers(float[] vert) { mVerticeBuffer = ByteBuffer.allocateDirect(vert.length * 4); mVerticeBuffer.order(ByteOrder.nativeOrder()); mVerticeBuffer.asFloatBuffer().put(vert); mVerticeBuffer.position(0); if (mCoordBuffer == null) { mCoordBuffer = ByteBuffer.allocateDirect(coordVertices.length * 4); mCoordBuffer.order(ByteOrder.nativeOrder()); mCoordBuffer.asFloatBuffer().put(coordVertices); mCoordBuffer.position(0); } } private void checkGlError(String op) { int error; while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { // throw new RuntimeException(op + ": glError " + error); } } } public static class GLFrameRenderer implements SurfaceHolder.Callback { private static final String TAG = GLFrameRenderer.class.getSimpleName(); private static final int SFPS = 25; private GLProgram prog = new GLProgram(0); private int mScreenWidth; private int mScreenHeight; private int mVideoWidth; private int mVideoHeight; private ByteBuffer y; private ByteBuffer u; private ByteBuffer v; private int mDisplayDegrees; private boolean mNeedMirror = false; private long mLastFrameTime = 0; private long mStanderDelta; public GLFrameRenderer() { // mParentAct = callback; mDisplayDegrees = 0; mNeedMirror = false; mStanderDelta = 1000 / SFPS; } public void setResolution(int nWidth, int nHeight) { mScreenWidth = nWidth; mScreenHeight = nHeight; } public void onDrawFrame() { synchronized (this) { if (mLastFrameTime == 0) { mLastFrameTime = System.currentTimeMillis(); } else { long currentTime = System.currentTimeMillis(); long delta = currentTime - mLastFrameTime; if (delta < mStanderDelta) { try { sleep(mStanderDelta - delta); } catch (InterruptedException e) { e.printStackTrace(); } } mLastFrameTime = currentTime; } 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); GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); prog.drawFrame(); } } } /** * this method will be called from native code, it happens when the video is about to play or * the video size changes. */ public void creatBuffer(int w, int h) { if (mScreenWidth > 0 && mScreenHeight > 0) { float f1 = 1f * mScreenHeight / mScreenWidth; float f2 = 1f * h / w; 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); byte[] yInit = new byte[yarraySize]; Arrays.fill(yInit, (byte) 0); y.put(yInit); byte[] uvInit = new byte[uvarraySize]; Arrays.fill(uvInit, (byte) 128); u = ByteBuffer.allocate(uvarraySize); u.put(uvInit); v = ByteBuffer.allocate(uvarraySize); v.put(uvInit); } } } /** * this method will be called from native code, it's used for passing yuv data to me. */ 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); } } public void update(byte[] yuv420p) { synchronized (this) { y.clear(); u.clear(); v.clear(); int size = this.mVideoWidth * this.mVideoHeight; y.put(yuv420p, 0, size); u.put(yuv420p, size, size / 4); v.put(yuv420p, size * 5 / 4, size / 4); } } public void setDisplayOrientation(int displayOrientation) { mDisplayDegrees = displayOrientation; prog.setDisplayOrientation(displayOrientation, mNeedMirror); } public void displayMirror(boolean mirror) { mNeedMirror = mirror; prog.setDisplayOrientation(mDisplayDegrees, mNeedMirror); } public void release() { prog.releaseProgram(); } public void creatProgram() { Log.e(TAG, "GLFrameRenderer :: onSurfaceCreated"); if (!prog.isProgramBuilt()) { prog.buildProgram(); } } @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { GLES20.glViewport(0, 0, width, height); } @Override public void surfaceDestroyed(SurfaceHolder holder) { } } public static class GLES20Support { public static boolean detectOpenGLES20(Context context) { ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); ConfigurationInfo info = am.getDeviceConfigurationInfo(); return (info.reqGlEsVersion >= 0x20000); } public static Dialog getNoSupportGLES20Dialog(final Activity activity) { AlertDialog.Builder b = new AlertDialog.Builder(activity); b.setCancelable(false); b.setTitle("no opengl"); b.setMessage("not support opengl"); b.setNegativeButton( "exit", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { activity.finish(); } }); return b.create(); } } }