32 | * The EGLContext must only be attached to one thread at a time. This class is not thread-safe. 33 | */ 34 | public final class EglCore { 35 | private static final String TAG = "EGLCore"; 36 | 37 | /** 38 | * Constructor flag: surface must be recordable. This discourages EGL from using a 39 | * pixel format that cannot be converted efficiently to something usable by the video 40 | * encoder. 41 | */ 42 | public static final int FLAG_RECORDABLE = 0x01; 43 | 44 | /** 45 | * Constructor flag: ask for GLES3, fall back to GLES2 if not available. Without this 46 | * flag, GLES2 is used. 47 | */ 48 | public static final int FLAG_TRY_GLES3 = 0x02; 49 | 50 | // Android-specific extension. 51 | private static final int EGL_RECORDABLE_ANDROID = 0x3142; 52 | 53 | private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY; 54 | private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT; 55 | private EGLConfig mEGLConfig = null; 56 | private int mGlVersion = -1; 57 | 58 | 59 | /** 60 | * Prepares EGL display and context. 61 | *
62 | * Equivalent to EglCore(null, 0). 63 | */ 64 | public EglCore() { 65 | this(null, 0); 66 | } 67 | 68 | /** 69 | * Prepares EGL display and context. 70 | *
71 | * @param sharedContext The context to share, or null if sharing is not desired. 72 | * @param flags Configuration bit flags, e.g. FLAG_RECORDABLE. 73 | */ 74 | public EglCore(EGLContext sharedContext, int flags) { 75 | if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { 76 | throw new RuntimeException("EGL already set up"); 77 | } 78 | 79 | if (sharedContext == null) { 80 | sharedContext = EGL14.EGL_NO_CONTEXT; 81 | } 82 | 83 | mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); 84 | if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { 85 | throw new RuntimeException("unable to get EGL14 display"); 86 | } 87 | int[] version = new int[2]; 88 | if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { 89 | mEGLDisplay = null; 90 | throw new RuntimeException("unable to initialize EGL14"); 91 | } 92 | 93 | // Try to get a GLES3 context, if requested. 94 | if ((flags & FLAG_TRY_GLES3) != 0) { 95 | //Log.d(TAG, "Trying GLES 3"); 96 | EGLConfig config = getConfig(flags, 3); 97 | if (config != null) { 98 | int[] attrib3_list = { 99 | EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, 100 | EGL14.EGL_NONE 101 | }; 102 | EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, 103 | attrib3_list, 0); 104 | 105 | if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) { 106 | //Log.d(TAG, "Got GLES 3 config"); 107 | mEGLConfig = config; 108 | mEGLContext = context; 109 | mGlVersion = 3; 110 | } 111 | } 112 | } 113 | if (mEGLContext == EGL14.EGL_NO_CONTEXT) { // GLES 2 only, or GLES 3 attempt failed 114 | //Log.d(TAG, "Trying GLES 2"); 115 | EGLConfig config = getConfig(flags, 2); 116 | if (config == null) { 117 | throw new RuntimeException("Unable to find a suitable EGLConfig"); 118 | } 119 | int[] attrib2_list = { 120 | EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, 121 | EGL14.EGL_NONE 122 | }; 123 | EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, 124 | attrib2_list, 0); 125 | checkEglError("eglCreateContext"); 126 | mEGLConfig = config; 127 | mEGLContext = context; 128 | mGlVersion = 2; 129 | } 130 | 131 | // Confirm with query. 132 | int[] values = new int[1]; 133 | EGL14.eglQueryContext(mEGLDisplay, mEGLContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, 134 | values, 0); 135 | Log.d(TAG, "EGLContext created, client version " + values[0]); 136 | } 137 | 138 | /** 139 | * Finds a suitable EGLConfig. 140 | * 141 | * @param flags Bit flags from constructor. 142 | * @param version Must be 2 or 3. 143 | */ 144 | private EGLConfig getConfig(int flags, int version) { 145 | int renderableType = EGL14.EGL_OPENGL_ES2_BIT; 146 | if (version >= 3) { 147 | renderableType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR; 148 | } 149 | 150 | // The actual surface is generally RGBA or RGBX, so situationally omitting alpha 151 | // doesn't really help. It can also lead to a huge performance hit on glReadPixels() 152 | // when reading into a GL_RGBA buffer. 153 | int[] attribList = { 154 | EGL14.EGL_RED_SIZE, 8, 155 | EGL14.EGL_GREEN_SIZE, 8, 156 | EGL14.EGL_BLUE_SIZE, 8, 157 | EGL14.EGL_ALPHA_SIZE, 8, 158 | //EGL14.EGL_DEPTH_SIZE, 16, 159 | //EGL14.EGL_STENCIL_SIZE, 8, 160 | EGL14.EGL_RENDERABLE_TYPE, renderableType, 161 | EGL14.EGL_NONE, 0, // placeholder for recordable [@-3] 162 | EGL14.EGL_NONE 163 | }; 164 | if ((flags & FLAG_RECORDABLE) != 0) { 165 | attribList[attribList.length - 3] = EGL_RECORDABLE_ANDROID; 166 | attribList[attribList.length - 2] = 1; 167 | } 168 | EGLConfig[] configs = new EGLConfig[1]; 169 | int[] numConfigs = new int[1]; 170 | if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length, 171 | numConfigs, 0)) { 172 | Log.w(TAG, "unable to find RGB8888 / " + version + " EGLConfig"); 173 | return null; 174 | } 175 | return configs[0]; 176 | } 177 | 178 | /** 179 | * Discards all resources held by this class, notably the EGL context. This must be 180 | * called from the thread where the context was created. 181 | *
182 | * On completion, no context will be current. 183 | */ 184 | public void release() { 185 | if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { 186 | // Android is unusual in that it uses a reference-counted EGLDisplay. So for 187 | // every eglInitialize() we need an eglTerminate(). 188 | EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, 189 | EGL14.EGL_NO_CONTEXT); 190 | EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); 191 | EGL14.eglReleaseThread(); 192 | EGL14.eglTerminate(mEGLDisplay); 193 | } 194 | 195 | mEGLDisplay = EGL14.EGL_NO_DISPLAY; 196 | mEGLContext = EGL14.EGL_NO_CONTEXT; 197 | mEGLConfig = null; 198 | } 199 | 200 | @Override 201 | protected void finalize() throws Throwable { 202 | try { 203 | if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { 204 | // We're limited here -- finalizers don't run on the thread that holds 205 | // the EGL state, so if a surface or context is still current on another 206 | // thread we can't fully release it here. Exceptions thrown from here 207 | // are quietly discarded. Complain in the log file. 208 | Log.w(TAG, "WARNING: EglCore was not explicitly released -- state may be leaked"); 209 | release(); 210 | } 211 | } finally { 212 | super.finalize(); 213 | } 214 | } 215 | 216 | /** 217 | * Destroys the specified surface. Note the EGLSurface won't actually be destroyed if it's 218 | * still current in a context. 219 | */ 220 | public void releaseSurface(EGLSurface eglSurface) { 221 | EGL14.eglDestroySurface(mEGLDisplay, eglSurface); 222 | } 223 | 224 | /** 225 | * Creates an EGL surface associated with a Surface. 226 | *
227 | * If this is destined for MediaCodec, the EGLConfig should have the "recordable" attribute. 228 | */ 229 | public EGLSurface createWindowSurface(Object surface) { 230 | if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture)) { 231 | throw new RuntimeException("invalid surface: " + surface); 232 | } 233 | 234 | // Create a window surface, and attach it to the Surface we received. 235 | int[] surfaceAttribs = { 236 | EGL14.EGL_NONE 237 | }; 238 | EGLSurface eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surface, 239 | surfaceAttribs, 0); 240 | checkEglError("eglCreateWindowSurface"); 241 | if (eglSurface == null) { 242 | throw new RuntimeException("surface was null"); 243 | } 244 | return eglSurface; 245 | } 246 | 247 | /** 248 | * Creates an EGL surface associated with an offscreen buffer. 249 | */ 250 | public EGLSurface createOffscreenSurface(int width, int height) { 251 | int[] surfaceAttribs = { 252 | EGL14.EGL_WIDTH, width, 253 | EGL14.EGL_HEIGHT, height, 254 | EGL14.EGL_NONE 255 | }; 256 | EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, 257 | surfaceAttribs, 0); 258 | checkEglError("eglCreatePbufferSurface"); 259 | if (eglSurface == null) { 260 | throw new RuntimeException("surface was null"); 261 | } 262 | return eglSurface; 263 | } 264 | 265 | /** 266 | * Makes our EGL context current, using the supplied surface for both "draw" and "read". 267 | */ 268 | public void makeCurrent(EGLSurface eglSurface) { 269 | if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { 270 | // called makeCurrent() before create? 271 | Log.d(TAG, "NOTE: makeCurrent w/o display"); 272 | } 273 | if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext)) { 274 | throw new RuntimeException("eglMakeCurrent failed"); 275 | } 276 | } 277 | 278 | /** 279 | * Makes our EGL context current, using the supplied "draw" and "read" surfaces. 280 | */ 281 | public void makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) { 282 | if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { 283 | // called makeCurrent() before create? 284 | Log.d(TAG, "NOTE: makeCurrent w/o display"); 285 | } 286 | if (!EGL14.eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext)) { 287 | throw new RuntimeException("eglMakeCurrent(draw,read) failed"); 288 | } 289 | } 290 | 291 | /** 292 | * Makes no context current. 293 | */ 294 | public void makeNothingCurrent() { 295 | if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, 296 | EGL14.EGL_NO_CONTEXT)) { 297 | throw new RuntimeException("eglMakeCurrent failed"); 298 | } 299 | } 300 | 301 | /** 302 | * Calls eglSwapBuffers. Use this to "publish" the current frame. 303 | * 304 | * @return false on failure 305 | */ 306 | public boolean swapBuffers(EGLSurface eglSurface) { 307 | return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface); 308 | } 309 | 310 | /** 311 | * Sends the presentation time stamp to EGL. Time is expressed in nanoseconds. 312 | */ 313 | public void setPresentationTime(EGLSurface eglSurface, long nsecs) { 314 | EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsecs); 315 | } 316 | 317 | /** 318 | * Returns true if our context and the specified surface are current. 319 | */ 320 | public boolean isCurrent(EGLSurface eglSurface) { 321 | return mEGLContext.equals(EGL14.eglGetCurrentContext()) && 322 | eglSurface.equals(EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW)); 323 | } 324 | 325 | /** 326 | * Performs a simple surface query. 327 | */ 328 | public int querySurface(EGLSurface eglSurface, int what) { 329 | int[] value = new int[1]; 330 | EGL14.eglQuerySurface(mEGLDisplay, eglSurface, what, value, 0); 331 | return value[0]; 332 | } 333 | 334 | /** 335 | * Queries a string value. 336 | */ 337 | public String queryString(int what) { 338 | return EGL14.eglQueryString(mEGLDisplay, what); 339 | } 340 | 341 | /** 342 | * Returns the GLES version this context is configured for (currently 2 or 3). 343 | */ 344 | public int getGlVersion() { 345 | return mGlVersion; 346 | } 347 | 348 | /** 349 | * Writes the current display, context, and surface to the log. 350 | */ 351 | public static void logCurrent(String msg) { 352 | EGLDisplay display; 353 | EGLContext context; 354 | EGLSurface surface; 355 | 356 | display = EGL14.eglGetCurrentDisplay(); 357 | context = EGL14.eglGetCurrentContext(); 358 | surface = EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW); 359 | Log.i(TAG, "Current EGL (" + msg + "): display=" + display + ", context=" + context + 360 | ", surface=" + surface); 361 | } 362 | 363 | /** 364 | * Checks for EGL errors. Throws an exception if an error has been raised. 365 | */ 366 | private void checkEglError(String msg) { 367 | int error; 368 | if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { 369 | throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error)); 370 | } 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /app/src/main/java/com/cgfay/eglnativerender/gles/EglSurfaceBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.cgfay.eglnativerender.gles; 18 | 19 | import android.graphics.Bitmap; 20 | import android.opengl.EGL14; 21 | import android.opengl.EGLSurface; 22 | import android.opengl.GLES30; 23 | import android.util.Log; 24 | 25 | 26 | import com.cgfay.eglnativerender.utils.BitmapUtils; 27 | import com.cgfay.eglnativerender.utils.GlUtil; 28 | 29 | import java.io.BufferedOutputStream; 30 | import java.io.File; 31 | import java.io.FileOutputStream; 32 | import java.io.IOException; 33 | import java.nio.ByteBuffer; 34 | import java.nio.ByteOrder; 35 | 36 | /** 37 | * Common base class for EGL surfaces. 38 | *
39 | * There can be multiple surfaces associated with a single context. 40 | */ 41 | public class EglSurfaceBase { 42 | protected static final String TAG = "EglSurfaceBase"; 43 | 44 | // EglCore object we're associated with. It may be associated with multiple surfaces. 45 | protected EglCore mEglCore; 46 | 47 | private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; 48 | private int mWidth = -1; 49 | private int mHeight = -1; 50 | 51 | protected EglSurfaceBase(EglCore eglCore) { 52 | mEglCore = eglCore; 53 | } 54 | 55 | /** 56 | * Creates a window surface. 57 | *
58 | * @param surface May be a Surface or SurfaceTexture. 59 | */ 60 | public void createWindowSurface(Object surface) { 61 | if (mEGLSurface != EGL14.EGL_NO_SURFACE) { 62 | throw new IllegalStateException("surface already created"); 63 | } 64 | mEGLSurface = mEglCore.createWindowSurface(surface); 65 | 66 | // Don't cache width/height here, because the size of the underlying surface can change 67 | // out from under us (see e.g. HardwareScalerActivity). 68 | //mWidth = mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH); 69 | //mHeight = mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT); 70 | } 71 | 72 | /** 73 | * Creates an off-screen surface. 74 | */ 75 | public void createOffscreenSurface(int width, int height) { 76 | if (mEGLSurface != EGL14.EGL_NO_SURFACE) { 77 | throw new IllegalStateException("surface already created"); 78 | } 79 | mEGLSurface = mEglCore.createOffscreenSurface(width, height); 80 | mWidth = width; 81 | mHeight = height; 82 | } 83 | 84 | /** 85 | * Returns the surface's width, in pixels. 86 | *
87 | * If this is called on a window surface, and the underlying surface is in the process 88 | * of changing size, we may not see the new size right away (e.g. in the "surfaceChanged" 89 | * callback). The size should match after the next buffer swap. 90 | */ 91 | public int getWidth() { 92 | if (mWidth < 0) { 93 | return mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH); 94 | } else { 95 | return mWidth; 96 | } 97 | } 98 | 99 | /** 100 | * Returns the surface's height, in pixels. 101 | */ 102 | public int getHeight() { 103 | if (mHeight < 0) { 104 | return mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT); 105 | } else { 106 | return mHeight; 107 | } 108 | } 109 | 110 | /** 111 | * Release the EGL surface. 112 | */ 113 | public void releaseEglSurface() { 114 | mEglCore.releaseSurface(mEGLSurface); 115 | mEGLSurface = EGL14.EGL_NO_SURFACE; 116 | mWidth = mHeight = -1; 117 | } 118 | 119 | /** 120 | * Makes our EGL context and surface current. 121 | */ 122 | public void makeCurrent() { 123 | mEglCore.makeCurrent(mEGLSurface); 124 | } 125 | 126 | /** 127 | * Makes our EGL context and surface current for drawing, using the supplied surface 128 | * for reading. 129 | */ 130 | public void makeCurrentReadFrom(EglSurfaceBase readSurface) { 131 | mEglCore.makeCurrent(mEGLSurface, readSurface.mEGLSurface); 132 | } 133 | 134 | /** 135 | * Calls eglSwapBuffers. Use this to "publish" the current frame. 136 | * 137 | * @return false on failure 138 | */ 139 | public boolean swapBuffers() { 140 | boolean result = mEglCore.swapBuffers(mEGLSurface); 141 | if (!result) { 142 | Log.d(TAG, "WARNING: swapBuffers() failed"); 143 | } 144 | return result; 145 | } 146 | 147 | /** 148 | * Sends the presentation time stamp to EGL. 149 | * 150 | * @param nsecs Timestamp, in nanoseconds. 151 | */ 152 | public void setPresentationTime(long nsecs) { 153 | mEglCore.setPresentationTime(mEGLSurface, nsecs); 154 | } 155 | 156 | /** 157 | * Saves the EGL surface to a file. 158 | *
159 | * Expects that this object's EGL surface is current. 160 | */ 161 | public void saveFrame(File file) throws IOException { 162 | if (!mEglCore.isCurrent(mEGLSurface)) { 163 | throw new RuntimeException("Expected EGL context/surface is not current"); 164 | } 165 | 166 | // glReadPixels fills in a "direct" ByteBuffer with what is essentially big-endian RGBA 167 | // data (i.e. a byte of red, followed by a byte of green...). While the Bitmap 168 | // constructor that takes an int[] wants little-endian ARGB (blue/red swapped), the 169 | // Bitmap "copy pixels" method wants the same format GL provides. 170 | // 171 | // Ideally we'd have some way to re-use the ByteBuffer, especially if we're calling 172 | // here often. 173 | // 174 | // Making this even more interesting is the upside-down nature of GL, which means 175 | // our output will look upside down relative to what appears on screen if the 176 | // typical GL conventions are used. 177 | 178 | String filename = file.toString(); 179 | 180 | int width = getWidth(); 181 | int height = getHeight(); 182 | ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4); 183 | buf.order(ByteOrder.LITTLE_ENDIAN); 184 | GLES30.glReadPixels(0, 0, width, height, 185 | GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, buf); 186 | GlUtil.checkGlError("glReadPixels"); 187 | buf.rewind(); 188 | 189 | BufferedOutputStream bos = null; 190 | try { 191 | bos = new BufferedOutputStream(new FileOutputStream(filename)); 192 | Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 193 | bmp.copyPixelsFromBuffer(buf); 194 | bmp = BitmapUtils.getRotatedBitmap(bmp, 180); 195 | bmp = BitmapUtils.flipBitmap(bmp); 196 | bmp.compress(Bitmap.CompressFormat.JPEG, 90, bos); 197 | bmp.recycle(); 198 | } finally { 199 | if (bos != null) bos.close(); 200 | } 201 | } 202 | 203 | /** 204 | * 获取帧 205 | * @return 206 | */ 207 | public Bitmap getFrameBitmap() { 208 | int width = getWidth(); 209 | int height = getHeight(); 210 | ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4); 211 | buf.order(ByteOrder.LITTLE_ENDIAN); 212 | GLES30.glReadPixels(0, 0, width, height, 213 | GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, buf); 214 | GlUtil.checkGlError("glReadPixels"); 215 | buf.rewind(); 216 | Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 217 | bmp.copyPixelsFromBuffer(buf); 218 | return BitmapUtils.getRotatedBitmap(bmp, 180); 219 | } 220 | 221 | /** 222 | * 获取当前帧的缓冲 223 | * @return 224 | */ 225 | public ByteBuffer getCurrentFrame() { 226 | int width = getWidth(); 227 | int height = getHeight(); 228 | ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4); 229 | buf.order(ByteOrder.LITTLE_ENDIAN); 230 | GLES30.glReadPixels(0, 0, width, height, 231 | GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, buf); 232 | GlUtil.checkGlError("glReadPixels"); 233 | buf.rewind(); 234 | return buf; 235 | } 236 | 237 | } 238 | -------------------------------------------------------------------------------- /app/src/main/java/com/cgfay/eglnativerender/gles/OffscreenSurface.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.cgfay.eglnativerender.gles; 18 | 19 | /** 20 | * Off-screen EGL surface (pbuffer). 21 | *
22 | * It's good practice to explicitly release() the surface, preferably from a "finally" block. 23 | */ 24 | public class OffscreenSurface extends EglSurfaceBase { 25 | /** 26 | * Creates an off-screen surface with the specified width and height. 27 | */ 28 | public OffscreenSurface(EglCore eglCore, int width, int height) { 29 | super(eglCore); 30 | createOffscreenSurface(width, height); 31 | } 32 | 33 | /** 34 | * Releases any resources associated with the surface. 35 | */ 36 | public void release() { 37 | releaseEglSurface(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/cgfay/eglnativerender/gles/WindowSurface.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.cgfay.eglnativerender.gles; 18 | 19 | import android.graphics.SurfaceTexture; 20 | import android.view.Surface; 21 | 22 | /** 23 | * Recordable EGL window surface. 24 | *
25 | * It's good practice to explicitly release() the surface, preferably from a "finally" block. 26 | */ 27 | public class WindowSurface extends EglSurfaceBase { 28 | private Surface mSurface; 29 | private boolean mReleaseSurface; 30 | 31 | /** 32 | * Associates an EGL surface with the native window surface. 33 | *
34 | * Set releaseSurface to true if you want the Surface to be released when release() is 35 | * called. This is convenient, but can interfere with framework classes that expect to 36 | * manage the Surface themselves (e.g. if you release a SurfaceView's Surface, the 37 | * surfaceDestroyed() callback won't fire). 38 | */ 39 | public WindowSurface(EglCore eglCore, Surface surface, boolean releaseSurface) { 40 | super(eglCore); 41 | createWindowSurface(surface); 42 | mSurface = surface; 43 | mReleaseSurface = releaseSurface; 44 | } 45 | 46 | /** 47 | * Associates an EGL surface with the SurfaceTexture. 48 | */ 49 | public WindowSurface(EglCore eglCore, SurfaceTexture surfaceTexture) { 50 | super(eglCore); 51 | createWindowSurface(surfaceTexture); 52 | } 53 | 54 | /** 55 | * Releases any resources associated with the EGL surface (and, if configured to do so, 56 | * with the Surface as well). 57 | *
58 | * Does not require that the surface's EGL context be current. 59 | */ 60 | public void release() { 61 | releaseEglSurface(); 62 | if (mSurface != null) { 63 | if (mReleaseSurface) { 64 | mSurface.release(); 65 | } 66 | mSurface = null; 67 | } 68 | } 69 | 70 | /** 71 | * Recreate the EGLSurface, using the new EglBase. The caller should have already 72 | * freed the old EGLSurface with releaseEglSurface(). 73 | *
74 | * This is useful when we want to update the EGLSurface associated with a Surface. 75 | * For example, if we want to share with a different EGLContext, which can only 76 | * be done by tearing down and recreating the context. (That's handled by the caller; 77 | * this just creates a new EGLSurface for the Surface we were handed earlier.) 78 | *
79 | * If the previous EGLSurface isn't fully destroyed, e.g. it's still current on a
80 | * context somewhere, the create call will fail with complaints from the Surface
81 | * about already being connected.
82 | */
83 | public void recreate(EglCore newEglCore) {
84 | if (mSurface == null) {
85 | throw new RuntimeException("not yet implemented for SurfaceTexture");
86 | }
87 | mEglCore = newEglCore; // switch to new context
88 | createWindowSurface(mSurface); // create new surface
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/app/src/main/java/com/cgfay/eglnativerender/utils/BitmapUtils.java:
--------------------------------------------------------------------------------
1 | package com.cgfay.eglnativerender.utils;
2 |
3 | import android.content.ContentProviderClient;
4 | import android.content.ContentResolver;
5 | import android.content.ContentValues;
6 | import android.content.Context;
7 | import android.content.res.AssetManager;
8 | import android.database.Cursor;
9 | import android.graphics.Bitmap;
10 | import android.graphics.BitmapFactory;
11 | import android.graphics.Matrix;
12 | import android.media.ExifInterface;
13 | import android.net.Uri;
14 | import android.os.Bundle;
15 | import android.os.RemoteException;
16 | import android.provider.MediaStore;
17 |
18 | import java.io.File;
19 | import java.io.FileOutputStream;
20 | import java.io.IOException;
21 | import java.io.InputStream;
22 |
23 | /**
24 | * Created by cain on 2017/7/9.
25 | */
26 |
27 | public class BitmapUtils {
28 |
29 | public static final String[] EXIF_TAGS = {
30 | "FNumber",
31 | ExifInterface.TAG_DATETIME,
32 | "ExposureTime",
33 | ExifInterface.TAG_FLASH,
34 | ExifInterface.TAG_FOCAL_LENGTH,
35 | "GPSAltitude", "GPSAltitudeRef",
36 | ExifInterface.TAG_GPS_DATESTAMP,
37 | ExifInterface.TAG_GPS_LATITUDE,
38 | ExifInterface.TAG_GPS_LATITUDE_REF,
39 | ExifInterface.TAG_GPS_LONGITUDE,
40 | ExifInterface.TAG_GPS_LONGITUDE_REF,
41 | ExifInterface.TAG_GPS_PROCESSING_METHOD,
42 | ExifInterface.TAG_GPS_TIMESTAMP,
43 | ExifInterface.TAG_IMAGE_LENGTH,
44 | ExifInterface.TAG_IMAGE_WIDTH, "ISOSpeedRatings",
45 | ExifInterface.TAG_MAKE, ExifInterface.TAG_MODEL,
46 | ExifInterface.TAG_WHITE_BALANCE,
47 | };
48 |
49 | /**
50 | * 旋转图片
51 | * @param bitmap
52 | * @param rotation
53 | * @return
54 | */
55 | public static Bitmap getRotatedBitmap(Bitmap bitmap, int rotation) {
56 | Matrix matrix = new Matrix();
57 | matrix.postRotate(rotation);
58 | return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
59 | bitmap.getHeight(), matrix, false);
60 | }
61 |
62 | /**
63 | * 镜像翻转图片
64 | * @param bitmap
65 | * @return
66 | */
67 | public static Bitmap flipBitmap(Bitmap bitmap) {
68 | return flipBitmap(bitmap, true, false);
69 | }
70 |
71 | /**
72 | * 翻转图片
73 | * @param bitmap
74 | * @param flipX
75 | * @param flipY
76 | * @return
77 | */
78 | public static Bitmap flipBitmap(Bitmap bitmap, boolean flipX, boolean flipY) {
79 | Matrix matrix = new Matrix();
80 | matrix.setScale(flipX ? -1 : 1, flipY ? -1 : 1);
81 | matrix.postTranslate(bitmap.getWidth(), 0);
82 | return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
83 | bitmap.getHeight(), matrix, false);
84 | }
85 |
86 |
87 | /**
88 | * 加载Assets文件夹下的图片
89 | * @param context
90 | * @param fileName
91 | * @return
92 | */
93 | public static Bitmap getImageFromAssetsFile(Context context, String fileName) {
94 | Bitmap bitmap = null;
95 | AssetManager manager = context.getResources().getAssets();
96 | try {
97 | InputStream is = manager.open(fileName);
98 | bitmap = BitmapFactory.decodeStream(is);
99 | is.close();
100 | } catch (IOException e) {
101 | e.printStackTrace();
102 | }
103 | return bitmap;
104 | }
105 |
106 | /**
107 | * 加载Assets文件夹下的图片
108 | * @param context
109 | * @param fileName
110 | * @return
111 | */
112 | public static Bitmap getImageFromAssetsFile(Context context, String fileName, Bitmap inBitmap) {
113 | Bitmap bitmap = null;
114 | AssetManager manager = context.getResources().getAssets();
115 | try {
116 | InputStream is = manager.open(fileName);
117 | if (inBitmap != null && !inBitmap.isRecycled()) {
118 | BitmapFactory.Options options = new BitmapFactory.Options();
119 | // 使用inBitmap时,inSampleSize得设置为1
120 | options.inSampleSize = 1;
121 | // 这个属性一定要在inBitmap之前使用,否则会弹出一下异常
122 | // BitmapFactory: Unable to reuse an immutable bitmap as an image decoder target.
123 | options.inMutable = true;
124 | options.inBitmap = inBitmap;
125 | bitmap = BitmapFactory.decodeStream(is, null, options);
126 | } else {
127 | bitmap = BitmapFactory.decodeStream(is);
128 | }
129 | is.close();
130 | } catch (IOException e) {
131 | e.printStackTrace();
132 | }
133 | return bitmap;
134 | }
135 |
136 | /**
137 | *
138 | * @param options
139 | * @param reqWidth
140 | * @param reqHeight
141 | * @return
142 | */
143 | private static int calculateInSampleSize(BitmapFactory.Options options,
144 | int reqWidth, int reqHeight) {
145 | // 计算原始图像的高度和宽度
146 | final int height = options.outHeight;
147 | final int width = options.outWidth;
148 | int inSampleSize = 1;
149 |
150 | // 当原始图像的高和宽大于所需高度和宽度时
151 | if (height > reqHeight || width > reqWidth) {
152 | final int heightRatio = Math.round((float) height / (float) reqHeight);
153 | final int widthRatio = Math.round((float) width / (float) reqWidth);
154 |
155 | // 算出长宽比后去比例小的作为inSamplesize,保证最后imageview的dimension比request的大
156 | inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
157 |
158 | // 计算总像素是否大于请求的宽高积的2倍
159 | final float totalPixels = width * height;
160 | final float totalReqPixelsCap = reqWidth * reqHeight * 2;
161 | while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
162 | inSampleSize++;
163 | }
164 | }
165 | return inSampleSize;
166 | }
167 |
168 |
169 | /**
170 | * 从文件读取Bitmap
171 | * @param dst
172 | * @param width
173 | * @param height
174 | * @return
175 | */
176 | public static Bitmap getBitmapFromFile(File dst, int width, int height) {
177 | if (null != dst && dst.exists()) {
178 | BitmapFactory.Options opts = null;
179 | if (width > 0 && height > 0) {
180 | opts = new BitmapFactory.Options();
181 | opts.inJustDecodeBounds = true;
182 | BitmapFactory.decodeFile(dst.getPath(), opts);
183 | // 计算图片缩放比例
184 | opts.inSampleSize = calculateInSampleSize(opts, width, height);
185 | opts.inJustDecodeBounds = false;
186 | opts.inInputShareable = true;
187 | opts.inPurgeable = true;
188 | }
189 | try {
190 | return BitmapFactory.decodeFile(dst.getPath(), opts);
191 | } catch (OutOfMemoryError e) {
192 | e.printStackTrace();
193 | }
194 | }
195 | return null;
196 | }
197 |
198 | /**
199 | * 保存图片
200 | * @param context
201 | * @param path
202 | * @param bitmap
203 | */
204 | public static void saveBitmap(Context context, String path, Bitmap bitmap) {
205 | saveBitmap(context, path, bitmap, true);
206 | }
207 |
208 | /**
209 | * 保存图片
210 | * @param context
211 | * @param path
212 | * @param bitmap
213 | * @param addToMediaStore
214 | */
215 | public static void saveBitmap(Context context, String path, Bitmap bitmap,
216 | boolean addToMediaStore) {
217 | final File file = new File(path);
218 | if (!file.getParentFile().exists()) {
219 | file.getParentFile().mkdirs();
220 | }
221 |
222 | FileOutputStream fOut = null;
223 | try {
224 | fOut = new FileOutputStream(file);
225 | } catch (IOException e) {
226 | e.printStackTrace();
227 | }
228 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut);
229 | try {
230 | fOut.flush();
231 | fOut.close();
232 | } catch (IOException e) {
233 | e.printStackTrace();
234 | }
235 | // 添加到媒体库
236 | if (addToMediaStore) {
237 | ContentValues values = new ContentValues();
238 | values.put(MediaStore.Images.Media.DATA, path);
239 | values.put(MediaStore.Images.Media.DISPLAY_NAME, file.getName());
240 | context.getContentResolver().insert(
241 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
242 | }
243 | }
244 |
245 | /**
246 | * 获取图片旋转角度
247 | * @param path
248 | * @return
249 | */
250 | public static int getOrientation(final String path) {
251 | int rotation = 0;
252 | try {
253 | File file = new File(path);
254 | ExifInterface exif = new ExifInterface(file.getAbsolutePath());
255 | int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
256 | ExifInterface.ORIENTATION_NORMAL);
257 | switch (orientation) {
258 | case ExifInterface.ORIENTATION_ROTATE_90:
259 | rotation = 90;
260 | break;
261 |
262 | case ExifInterface.ORIENTATION_ROTATE_180:
263 | rotation = 180;
264 | break;
265 |
266 | case ExifInterface.ORIENTATION_ROTATE_270:
267 | rotation = 270;
268 | break;
269 |
270 | default:
271 | rotation = 0;
272 | break;
273 | }
274 | } catch (IOException e) {
275 | e.printStackTrace();
276 | }
277 | return rotation;
278 | }
279 |
280 | /**
281 | * 获取Uri路径图片的旋转角度
282 | * @param context
283 | * @param uri
284 | * @return
285 | */
286 | public static int getOrientation(Context context, Uri uri) {
287 | final String scheme = uri.getScheme();
288 | ContentProviderClient provider = null;
289 | if (scheme == null || ContentResolver.SCHEME_FILE.equals(scheme)) {
290 | return getOrientation(uri.getPath());
291 | } else if (scheme.equals(ContentResolver.SCHEME_CONTENT)) {
292 | try {
293 | provider = context.getContentResolver().acquireContentProviderClient(uri);
294 | } catch (SecurityException e) {
295 | return 0;
296 | }
297 | if (provider != null) {
298 | Cursor cursor;
299 | try {
300 | cursor = provider.query(uri, new String[] {
301 | MediaStore.Images.ImageColumns.ORIENTATION,
302 | MediaStore.Images.ImageColumns.DATA},
303 | null, null, null);
304 | } catch (RemoteException e) {
305 | e.printStackTrace();
306 | return 0;
307 | }
308 | if (cursor == null) {
309 | return 0;
310 | }
311 |
312 | int orientationIndex = cursor
313 | .getColumnIndex(MediaStore.Images.ImageColumns.ORIENTATION);
314 | int dataIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
315 |
316 | try {
317 | if (cursor.getCount() > 0) {
318 | cursor.moveToFirst();
319 |
320 | int rotation = 0;
321 |
322 | if (orientationIndex > -1) {
323 | rotation = cursor.getInt(orientationIndex);
324 | }
325 |
326 | if (dataIndex > -1) {
327 | String path = cursor.getString(dataIndex);
328 | rotation |= getOrientation(path);
329 | }
330 | return rotation;
331 | }
332 | } finally {
333 | cursor.close();
334 | }
335 | }
336 | }
337 | return 0;
338 | }
339 |
340 | /**
341 | * 获取图片大小
342 | * @param path
343 | * @return
344 | */
345 | public static Size getBitmapSize(String path) {
346 | BitmapFactory.Options options = new BitmapFactory.Options();
347 | options.inJustDecodeBounds = true;
348 | BitmapFactory.decodeFile(path, options);
349 | return new Size(options.outWidth, options.outHeight);
350 | }
351 |
352 | /**
353 | * 将Bitmap图片旋转90度
354 | * @param data
355 | * @return
356 | */
357 | public static Bitmap rotateBitmap(byte[] data) {
358 | return rotateBitmap(data, 90);
359 | }
360 |
361 | /**
362 | * 将Bitmap图片旋转一定角度
363 | * @param data
364 | * @param rotate
365 | * @return
366 | */
367 | public static Bitmap rotateBitmap(byte[] data, int rotate) {
368 | Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
369 | Matrix matrix = new Matrix();
370 | matrix.reset();
371 | matrix.postRotate(rotate);
372 | Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
373 | bitmap.getHeight(), matrix, true);
374 | bitmap.recycle();
375 | System.gc();
376 | return rotatedBitmap;
377 | }
378 |
379 | /**
380 | * 将Bitmap图片旋转90度
381 | * @param bitmap
382 | * @return
383 | */
384 | public static Bitmap rotateBitmap(Bitmap bitmap) {
385 | return rotateBitmap(bitmap, 90);
386 | }
387 |
388 | /**
389 | * 将Bitmap图片旋转一定角度
390 | * @param bitmap
391 | * @param rotate
392 | * @return
393 | */
394 | public static Bitmap rotateBitmap(Bitmap bitmap, int rotate) {
395 | Matrix matrix = new Matrix();
396 | matrix.reset();
397 | matrix.postRotate(rotate);
398 | Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
399 | bitmap.getHeight(), matrix, true);
400 | return rotatedBitmap;
401 | }
402 |
403 | /**
404 | * 获取Exif参数
405 | * @param path
406 | * @param bundle
407 | * @return
408 | */
409 | public static boolean loadExifAttributes(String path, Bundle bundle) {
410 | ExifInterface exifInterface;
411 | try {
412 | exifInterface = new ExifInterface(path);
413 | } catch (IOException e) {
414 | e.printStackTrace();
415 | return false;
416 | }
417 | for (String tag : EXIF_TAGS) {
418 | bundle.putString(tag, exifInterface.getAttribute(tag));
419 | }
420 | return true;
421 | }
422 |
423 | /**
424 | * 保存Exif属性
425 | * @param path
426 | * @param bundle
427 | * @return
428 | */
429 | public static boolean saveExifAttributes(String path, Bundle bundle) {
430 | ExifInterface exif;
431 | try {
432 | exif = new ExifInterface(path);
433 | } catch (IOException e) {
434 | e.printStackTrace();
435 | return false;
436 | }
437 |
438 | for (String tag : EXIF_TAGS) {
439 | if (bundle.containsKey(tag)) {
440 | exif.setAttribute(tag, bundle.getString(tag));
441 | }
442 | }
443 | try {
444 | exif.saveAttributes();
445 | } catch (IOException e) {
446 | e.printStackTrace();
447 | return false;
448 | }
449 |
450 | return true;
451 | }
452 | }
453 |
--------------------------------------------------------------------------------
/app/src/main/java/com/cgfay/eglnativerender/utils/Size.java:
--------------------------------------------------------------------------------
1 | package com.cgfay.eglnativerender.utils;
2 |
3 | /**
4 | * Created by cain.huang on 2017/7/27.
5 | */
6 |
7 | public class Size {
8 | int mWidth;
9 | int mHeight;
10 |
11 | public Size() {
12 | }
13 |
14 | public Size(int width, int height) {
15 | mWidth = width;
16 | mHeight = height;
17 | }
18 |
19 | public int getWidth() {
20 | return mWidth;
21 | }
22 |
23 | public void setWidth(int width) {
24 | mWidth = width;
25 | }
26 |
27 | public int getHeight() {
28 | return mHeight;
29 | }
30 |
31 | public void setHeight(int height) {
32 | this.mHeight = height;
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/cgfay/eglnativerender/utils/TextureRotationUtils.java:
--------------------------------------------------------------------------------
1 | package com.cgfay.eglnativerender.utils;
2 |
3 | import android.hardware.Camera;
4 |
5 | import java.nio.FloatBuffer;
6 |
7 | /**
8 | * Created by cain on 17-7-26.
9 | */
10 |
11 | public class TextureRotationUtils {
12 |
13 | // 摄像头是否倒置,主要是应对Nexus 5X (bullhead) 的后置摄像头图像倒置的问题
14 | private static boolean mBackReverse = false;
15 |
16 | public static final int CoordsPerVertex = 3;
17 |
18 | public static final float CubeVertices[] = {
19 | -1.0f, -1.0f, 0.0f, // 0 bottom left
20 | 1.0f, -1.0f, 0.0f, // 1 bottom right
21 | -1.0f, 1.0f, 0.0f, // 2 top left
22 | 1.0f, 1.0f, 0.0f, // 3 top right
23 | };
24 |
25 | public static final float TextureVertices[] = {
26 | 0.0f, 0.0f, // 0 bottom left
27 | 1.0f, 0.0f, // 1 bottom right
28 | 0.0f, 1.0f, // 2 top left
29 | 1.0f, 1.0f // 3 top right
30 | };
31 |
32 | public static final float TextureVertices_90[] = {
33 | 1.0f, 0.0f,
34 | 1.0f, 1.0f,
35 | 0.0f, 0.0f,
36 | 0.0f, 1.0f,
37 | };
38 |
39 | public static final float TextureVertices_180[] = {
40 | 1.0f, 1.0f,
41 | 0.0f, 1.0f,
42 | 1.0f, 0.0f,
43 | 0.0f, 0.0f,
44 | };
45 |
46 | public static final float TextureVertices_270[] = {
47 | 0.0f, 1.0f,
48 | 0.0f, 0.0f,
49 | 1.0f, 1.0f,
50 | 0.0f, 0.0f,
51 | };
52 |
53 | public static float[] getTextureVertices() {
54 | return TextureVertices;
55 | }
56 |
57 | public static FloatBuffer getTextureBuffer() {
58 | return GlUtil.createFloatBuffer(TextureRotationUtils.TextureVertices);
59 | }
60 |
61 | public static void setBackReverse(boolean reverse) {
62 | mBackReverse = reverse;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |