49 | * Triangles are 0-1-2 and 2-1-3 (counter-clockwise winding). 50 | */ 51 | private static final float RECTANGLE_COORDS[] = { 52 | -0.5f, -0.5f, // 0 bottom left 53 | 0.5f, -0.5f, // 1 bottom right 54 | -0.5f, 0.5f, // 2 top left 55 | 0.5f, 0.5f, // 3 top right 56 | }; 57 | private static final float RECTANGLE_TEX_COORDS[] = { 58 | 0.0f, 1.0f, // 0 bottom left 59 | 1.0f, 1.0f, // 1 bottom right 60 | 0.0f, 0.0f, // 2 top left 61 | 1.0f, 0.0f // 3 top right 62 | }; 63 | private static final FloatBuffer RECTANGLE_BUF = 64 | GlUtil.createFloatBuffer(RECTANGLE_COORDS); 65 | private static final FloatBuffer RECTANGLE_TEX_BUF = 66 | GlUtil.createFloatBuffer(RECTANGLE_TEX_COORDS); 67 | 68 | /** 69 | * A "full" square, extending from -1 to +1 in both dimensions. When the model/view/projection 70 | * matrix is identity, this will exactly cover the viewport. 71 | *
72 | * The texture coordinates are Y-inverted relative to RECTANGLE. (This seems to work out 73 | * right with external textures from SurfaceTexture.) 74 | */ 75 | private static final float FULL_RECTANGLE_COORDS[] = { 76 | -1.0f, -1.0f, // 0 bottom left 77 | 1.0f, -1.0f, // 1 bottom right 78 | -1.0f, 1.0f, // 2 top left 79 | 1.0f, 1.0f, // 3 top right 80 | }; 81 | private static final float FULL_RECTANGLE_TEX_COORDS[] = { 82 | 0.0f, 0.0f, // 0 bottom left 83 | 1.0f, 0.0f, // 1 bottom right 84 | 0.0f, 1.0f, // 2 top left 85 | 1.0f, 1.0f // 3 top right 86 | }; 87 | private static final FloatBuffer FULL_RECTANGLE_BUF = 88 | GlUtil.createFloatBuffer(FULL_RECTANGLE_COORDS); 89 | private static final FloatBuffer FULL_RECTANGLE_TEX_BUF = 90 | GlUtil.createFloatBuffer(FULL_RECTANGLE_TEX_COORDS); 91 | 92 | 93 | private FloatBuffer mVertexArray; 94 | private FloatBuffer mTexCoordArray; 95 | private int mVertexCount; 96 | private int mCoordsPerVertex; 97 | private int mVertexStride; 98 | private int mTexCoordStride; 99 | private Prefab mPrefab; 100 | 101 | /** 102 | * Enum values for constructor. 103 | */ 104 | public enum Prefab { 105 | TRIANGLE, RECTANGLE, FULL_RECTANGLE 106 | } 107 | 108 | /** 109 | * Prepares a drawable from a "pre-fabricated" shape definition. 110 | *
111 | * Does no EGL/GL operations, so this can be done at any time. 112 | */ 113 | public Drawable2d(Prefab shape) { 114 | switch (shape) { 115 | case TRIANGLE: 116 | mVertexArray = TRIANGLE_BUF; 117 | mTexCoordArray = TRIANGLE_TEX_BUF; 118 | mCoordsPerVertex = 2; 119 | mVertexStride = mCoordsPerVertex * SIZEOF_FLOAT; 120 | mVertexCount = TRIANGLE_COORDS.length / mCoordsPerVertex; 121 | break; 122 | case RECTANGLE: 123 | mVertexArray = RECTANGLE_BUF; 124 | mTexCoordArray = RECTANGLE_TEX_BUF; 125 | mCoordsPerVertex = 2; 126 | mVertexStride = mCoordsPerVertex * SIZEOF_FLOAT; 127 | mVertexCount = RECTANGLE_COORDS.length / mCoordsPerVertex; 128 | break; 129 | case FULL_RECTANGLE: 130 | mVertexArray = FULL_RECTANGLE_BUF; 131 | mTexCoordArray = FULL_RECTANGLE_TEX_BUF; 132 | mCoordsPerVertex = 2; 133 | mVertexStride = mCoordsPerVertex * SIZEOF_FLOAT; 134 | mVertexCount = FULL_RECTANGLE_COORDS.length / mCoordsPerVertex; 135 | break; 136 | default: 137 | throw new RuntimeException("Unknown shape " + shape); 138 | } 139 | mTexCoordStride = 2 * SIZEOF_FLOAT; 140 | mPrefab = shape; 141 | } 142 | 143 | /** 144 | * Returns the array of vertices. 145 | *
146 | * To avoid allocations, this returns internal state. The caller must not modify it. 147 | */ 148 | public FloatBuffer getVertexArray() { 149 | return mVertexArray; 150 | } 151 | 152 | /** 153 | * Returns the array of texture coordinates. 154 | *
155 | * To avoid allocations, this returns internal state. The caller must not modify it. 156 | */ 157 | public FloatBuffer getTexCoordArray() { 158 | return mTexCoordArray; 159 | } 160 | 161 | /** 162 | * Returns the number of vertices stored in the vertex array. 163 | */ 164 | public int getVertexCount() { 165 | return mVertexCount; 166 | } 167 | 168 | /** 169 | * Returns the width, in bytes, of the data for each vertex. 170 | */ 171 | public int getVertexStride() { 172 | return mVertexStride; 173 | } 174 | 175 | /** 176 | * Returns the width, in bytes, of the data for each texture coordinate. 177 | */ 178 | public int getTexCoordStride() { 179 | return mTexCoordStride; 180 | } 181 | 182 | /** 183 | * Returns the number of position coordinates per vertex. This will be 2 or 3. 184 | */ 185 | public int getCoordsPerVertex() { 186 | return mCoordsPerVertex; 187 | } 188 | 189 | @Override 190 | public String toString() { 191 | if (mPrefab != null) { 192 | return "[Drawable2d: " + mPrefab + "]"; 193 | } else { 194 | return "[Drawable2d: ...]"; 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /rtmppublisher/src/main/java/com/takusemba/rtmppublisher/gles/EglCore.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.takusemba.rtmppublisher.gles; 18 | 19 | import android.graphics.SurfaceTexture; 20 | import android.opengl.EGL14; 21 | import android.opengl.EGLConfig; 22 | import android.opengl.EGLContext; 23 | import android.opengl.EGLDisplay; 24 | import android.opengl.EGLExt; 25 | import android.opengl.EGLSurface; 26 | import android.util.Log; 27 | import android.view.Surface; 28 | 29 | /** 30 | * Core EGL state (display, context, config). 31 | *
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 = GlUtil.TAG; 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 | -------------------------------------------------------------------------------- /rtmppublisher/src/main/java/com/takusemba/rtmppublisher/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.takusemba.rtmppublisher.gles; 18 | 19 | import android.graphics.Bitmap; 20 | import android.opengl.EGL14; 21 | import android.opengl.EGLSurface; 22 | import android.opengl.GLES20; 23 | import android.util.Log; 24 | import java.io.BufferedOutputStream; 25 | import java.io.File; 26 | import java.io.FileOutputStream; 27 | import java.io.IOException; 28 | import java.nio.ByteBuffer; 29 | import java.nio.ByteOrder; 30 | 31 | /** 32 | * Common base class for EGL surfaces. 33 | *
34 | * There can be multiple surfaces associated with a single context. 35 | */ 36 | public class EglSurfaceBase { 37 | protected static final String TAG = GlUtil.TAG; 38 | 39 | // EglCore object we're associated with. It may be associated with multiple surfaces. 40 | protected EglCore mEglCore; 41 | 42 | private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; 43 | private int mWidth = -1; 44 | private int mHeight = -1; 45 | 46 | protected EglSurfaceBase(EglCore eglCore) { 47 | mEglCore = eglCore; 48 | } 49 | 50 | /** 51 | * Creates a window surface. 52 | *
53 | * @param surface May be a Surface or SurfaceTexture. 54 | */ 55 | public void createWindowSurface(Object surface) { 56 | if (mEGLSurface != EGL14.EGL_NO_SURFACE) { 57 | throw new IllegalStateException("surface already created"); 58 | } 59 | mEGLSurface = mEglCore.createWindowSurface(surface); 60 | 61 | // Don't cache width/height here, because the size of the underlying surface can change 62 | // out from under us (see e.g. HardwareScalerActivity). 63 | //mWidth = mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH); 64 | //mHeight = mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT); 65 | } 66 | 67 | /** 68 | * Creates an off-screen surface. 69 | */ 70 | public void createOffscreenSurface(int width, int height) { 71 | if (mEGLSurface != EGL14.EGL_NO_SURFACE) { 72 | throw new IllegalStateException("surface already created"); 73 | } 74 | mEGLSurface = mEglCore.createOffscreenSurface(width, height); 75 | mWidth = width; 76 | mHeight = height; 77 | } 78 | 79 | /** 80 | * Returns the surface's width, in pixels. 81 | *
82 | * If this is called on a window surface, and the underlying surface is in the process 83 | * of changing size, we may not see the new size right away (e.g. in the "surfaceChanged" 84 | * callback). The size should match after the next buffer swap. 85 | */ 86 | public int getWidth() { 87 | if (mWidth < 0) { 88 | return mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH); 89 | } else { 90 | return mWidth; 91 | } 92 | } 93 | 94 | /** 95 | * Returns the surface's height, in pixels. 96 | */ 97 | public int getHeight() { 98 | if (mHeight < 0) { 99 | return mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT); 100 | } else { 101 | return mHeight; 102 | } 103 | } 104 | 105 | /** 106 | * Release the EGL surface. 107 | */ 108 | public void releaseEglSurface() { 109 | mEglCore.releaseSurface(mEGLSurface); 110 | mEGLSurface = EGL14.EGL_NO_SURFACE; 111 | mWidth = mHeight = -1; 112 | } 113 | 114 | /** 115 | * Makes our EGL context and surface current. 116 | */ 117 | public void makeCurrent() { 118 | mEglCore.makeCurrent(mEGLSurface); 119 | } 120 | 121 | /** 122 | * Makes our EGL context and surface current for drawing, using the supplied surface 123 | * for reading. 124 | */ 125 | public void makeCurrentReadFrom(EglSurfaceBase readSurface) { 126 | mEglCore.makeCurrent(mEGLSurface, readSurface.mEGLSurface); 127 | } 128 | 129 | /** 130 | * Calls eglSwapBuffers. Use this to "publish" the current frame. 131 | * 132 | * @return false on failure 133 | */ 134 | public boolean swapBuffers() { 135 | boolean result = mEglCore.swapBuffers(mEGLSurface); 136 | if (!result) { 137 | Log.d(TAG, "WARNING: swapBuffers() failed"); 138 | } 139 | return result; 140 | } 141 | 142 | /** 143 | * Sends the presentation time stamp to EGL. 144 | * 145 | * @param nsecs Timestamp, in nanoseconds. 146 | */ 147 | public void setPresentationTime(long nsecs) { 148 | mEglCore.setPresentationTime(mEGLSurface, nsecs); 149 | } 150 | 151 | /** 152 | * Saves the EGL surface to a file. 153 | *
154 | * Expects that this object's EGL surface is current. 155 | */ 156 | public void saveFrame(File file) throws IOException { 157 | if (!mEglCore.isCurrent(mEGLSurface)) { 158 | throw new RuntimeException("Expected EGL context/surface is not current"); 159 | } 160 | 161 | // glReadPixels fills in a "direct" ByteBuffer with what is essentially big-endian RGBA 162 | // data (i.e. a byte of red, followed by a byte of green...). While the Bitmap 163 | // constructor that takes an int[] wants little-endian ARGB (blue/red swapped), the 164 | // Bitmap "copy pixels" method wants the same format GL provides. 165 | // 166 | // Ideally we'd have some way to re-use the ByteBuffer, especially if we're calling 167 | // here often. 168 | // 169 | // Making this even more interesting is the upside-down nature of GL, which means 170 | // our output will look upside down relative to what appears on screen if the 171 | // typical GL conventions are used. 172 | 173 | String filename = file.toString(); 174 | 175 | int width = getWidth(); 176 | int height = getHeight(); 177 | ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4); 178 | buf.order(ByteOrder.LITTLE_ENDIAN); 179 | GLES20.glReadPixels(0, 0, width, height, 180 | GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf); 181 | GlUtil.checkGlError("glReadPixels"); 182 | buf.rewind(); 183 | 184 | BufferedOutputStream bos = null; 185 | try { 186 | bos = new BufferedOutputStream(new FileOutputStream(filename)); 187 | Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 188 | bmp.copyPixelsFromBuffer(buf); 189 | bmp.compress(Bitmap.CompressFormat.PNG, 90, bos); 190 | bmp.recycle(); 191 | } finally { 192 | if (bos != null) bos.close(); 193 | } 194 | Log.d(TAG, "Saved " + width + "x" + height + " frame as '" + filename + "'"); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /rtmppublisher/src/main/java/com/takusemba/rtmppublisher/gles/FlatShadedProgram.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 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.takusemba.rtmppublisher.gles; 18 | 19 | import android.opengl.GLES20; 20 | import android.util.Log; 21 | import java.nio.FloatBuffer; 22 | 23 | /** 24 | * GL program and supporting functions for flat-shaded rendering. 25 | */ 26 | public class FlatShadedProgram { 27 | private static final String TAG = GlUtil.TAG; 28 | 29 | private static final String VERTEX_SHADER = 30 | "uniform mat4 uMVPMatrix;" + 31 | "attribute vec4 aPosition;" + 32 | "void main() {" + 33 | " gl_Position = uMVPMatrix * aPosition;" + 34 | "}"; 35 | 36 | private static final String FRAGMENT_SHADER = 37 | "precision mediump float;" + 38 | "uniform vec4 uColor;" + 39 | "void main() {" + 40 | " gl_FragColor = uColor;" + 41 | "}"; 42 | 43 | // Handles to the GL program and various components of it. 44 | private int mProgramHandle = -1; 45 | private int muColorLoc = -1; 46 | private int muMVPMatrixLoc = -1; 47 | private int maPositionLoc = -1; 48 | 49 | 50 | /** 51 | * Prepares the program in the current EGL context. 52 | */ 53 | public FlatShadedProgram() { 54 | mProgramHandle = GlUtil.createProgram(VERTEX_SHADER, FRAGMENT_SHADER); 55 | if (mProgramHandle == 0) { 56 | throw new RuntimeException("Unable to create program"); 57 | } 58 | Log.d(TAG, "Created program " + mProgramHandle); 59 | 60 | // get locations of attributes and uniforms 61 | 62 | maPositionLoc = GLES20.glGetAttribLocation(mProgramHandle, "aPosition"); 63 | GlUtil.checkLocation(maPositionLoc, "aPosition"); 64 | muMVPMatrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "uMVPMatrix"); 65 | GlUtil.checkLocation(muMVPMatrixLoc, "uMVPMatrix"); 66 | muColorLoc = GLES20.glGetUniformLocation(mProgramHandle, "uColor"); 67 | GlUtil.checkLocation(muColorLoc, "uColor"); 68 | } 69 | 70 | /** 71 | * Releases the program. 72 | */ 73 | public void release() { 74 | GLES20.glDeleteProgram(mProgramHandle); 75 | mProgramHandle = -1; 76 | } 77 | 78 | /** 79 | * Issues the draw call. Does the full setup on every call. 80 | * 81 | * @param mvpMatrix The 4x4 projection matrix. 82 | * @param color A 4-element color vector. 83 | * @param vertexBuffer Buffer with vertex data. 84 | * @param firstVertex Index of first vertex to use in vertexBuffer. 85 | * @param vertexCount Number of vertices in vertexBuffer. 86 | * @param coordsPerVertex The number of coordinates per vertex (e.g. x,y is 2). 87 | * @param vertexStride Width, in bytes, of the data for each vertex (often vertexCount * 88 | * sizeof(float)). 89 | */ 90 | public void draw(float[] mvpMatrix, float[] color, FloatBuffer vertexBuffer, 91 | int firstVertex, int vertexCount, int coordsPerVertex, int vertexStride) { 92 | GlUtil.checkGlError("draw start"); 93 | 94 | // Select the program. 95 | GLES20.glUseProgram(mProgramHandle); 96 | GlUtil.checkGlError("glUseProgram"); 97 | 98 | // Copy the model / view / projection matrix over. 99 | GLES20.glUniformMatrix4fv(muMVPMatrixLoc, 1, false, mvpMatrix, 0); 100 | GlUtil.checkGlError("glUniformMatrix4fv"); 101 | 102 | // Copy the color vector in. 103 | GLES20.glUniform4fv(muColorLoc, 1, color, 0); 104 | GlUtil.checkGlError("glUniform4fv "); 105 | 106 | // Enable the "aPosition" vertex attribute. 107 | GLES20.glEnableVertexAttribArray(maPositionLoc); 108 | GlUtil.checkGlError("glEnableVertexAttribArray"); 109 | 110 | // Connect vertexBuffer to "aPosition". 111 | GLES20.glVertexAttribPointer(maPositionLoc, coordsPerVertex, 112 | GLES20.GL_FLOAT, false, vertexStride, vertexBuffer); 113 | GlUtil.checkGlError("glVertexAttribPointer"); 114 | 115 | // Draw the rect. 116 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, firstVertex, vertexCount); 117 | GlUtil.checkGlError("glDrawArrays"); 118 | 119 | // Done -- disable vertex array and program. 120 | GLES20.glDisableVertexAttribArray(maPositionLoc); 121 | GLES20.glUseProgram(0); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /rtmppublisher/src/main/java/com/takusemba/rtmppublisher/gles/FullFrameRect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 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.takusemba.rtmppublisher.gles; 18 | 19 | /** 20 | * This class essentially represents a viewport-sized sprite that will be rendered with 21 | * a texture, usually from an external source like the camera or video decoder. 22 | */ 23 | public class FullFrameRect { 24 | private final Drawable2d 25 | mRectDrawable = new Drawable2d(Drawable2d.Prefab.FULL_RECTANGLE); 26 | private Texture2dProgram mProgram; 27 | 28 | /** 29 | * Prepares the object. 30 | * 31 | * @param program The program to use. FullFrameRect takes ownership, and will release 32 | * the program when no longer needed. 33 | */ 34 | public FullFrameRect(Texture2dProgram program) { 35 | mProgram = program; 36 | } 37 | 38 | /** 39 | * Releases resources. 40 | *
41 | * This must be called with the appropriate EGL context current (i.e. the one that was 42 | * current when the constructor was called). If we're about to destroy the EGL context, 43 | * there's no value in having the caller make it current just to do this cleanup, so you 44 | * can pass a flag that will tell this function to skip any EGL-context-specific cleanup. 45 | */ 46 | public void release(boolean doEglCleanup) { 47 | if (mProgram != null) { 48 | if (doEglCleanup) { 49 | mProgram.release(); 50 | } 51 | mProgram = null; 52 | } 53 | } 54 | 55 | /** 56 | * Returns the program currently in use. 57 | */ 58 | public Texture2dProgram getProgram() { 59 | return mProgram; 60 | } 61 | 62 | /** 63 | * Changes the program. The previous program will be released. 64 | *
65 | * The appropriate EGL context must be current. 66 | */ 67 | public void changeProgram(Texture2dProgram program) { 68 | mProgram.release(); 69 | mProgram = program; 70 | } 71 | 72 | /** 73 | * Creates a texture object suitable for use with drawFrame(). 74 | */ 75 | public int createTextureObject() { 76 | return mProgram.createTextureObject(); 77 | } 78 | 79 | /** 80 | * Draws a viewport-filling rect, texturing it with the specified texture object. 81 | */ 82 | public void drawFrame(int textureId, float[] texMatrix) { 83 | // Use the identity matrix for MVP so our 2x2 FULL_RECTANGLE covers the viewport. 84 | mProgram.draw(GlUtil.IDENTITY_MATRIX, mRectDrawable.getVertexArray(), 0, 85 | mRectDrawable.getVertexCount(), mRectDrawable.getCoordsPerVertex(), 86 | mRectDrawable.getVertexStride(), 87 | texMatrix, mRectDrawable.getTexCoordArray(), textureId, 88 | mRectDrawable.getTexCoordStride()); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /rtmppublisher/src/main/java/com/takusemba/rtmppublisher/gles/GeneratedTexture.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 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.takusemba.rtmppublisher.gles; 18 | 19 | import android.opengl.GLES20; 20 | import java.nio.ByteBuffer; 21 | 22 | /** 23 | * Code for generating images useful for testing textures. 24 | */ 25 | public class GeneratedTexture { 26 | //private static final String TAG = GlUtil.TAG; 27 | 28 | public enum Image { COARSE, FINE }; 29 | 30 | // Basic colors, in little-endian RGBA. 31 | private static final int BLACK = 0x00000000; 32 | private static final int RED = 0x000000ff; 33 | private static final int GREEN = 0x0000ff00; 34 | private static final int BLUE = 0x00ff0000; 35 | private static final int MAGENTA = RED | BLUE; 36 | private static final int YELLOW = RED | GREEN; 37 | private static final int CYAN = GREEN | BLUE; 38 | private static final int WHITE = RED | GREEN | BLUE; 39 | private static final int OPAQUE = (int) 0xff000000L; 40 | private static final int HALF = (int) 0x80000000L; 41 | private static final int LOW = (int) 0x40000000L; 42 | private static final int TRANSP = 0; 43 | 44 | private static final int GRID[] = new int[] { // must be 16 elements 45 | OPAQUE|RED, OPAQUE|YELLOW, OPAQUE|GREEN, OPAQUE|MAGENTA, 46 | OPAQUE|WHITE, LOW|RED, LOW|GREEN, OPAQUE|YELLOW, 47 | OPAQUE|MAGENTA, TRANSP|GREEN, HALF|RED, OPAQUE|BLACK, 48 | OPAQUE|CYAN, OPAQUE|MAGENTA, OPAQUE|CYAN, OPAQUE|BLUE, 49 | }; 50 | 51 | private static final int TEX_SIZE = 64; // must be power of 2 52 | private static final int FORMAT = GLES20.GL_RGBA; 53 | private static final int BYTES_PER_PIXEL = 4; // RGBA 54 | 55 | // Generate test image data. This must come after the other values are initialized. 56 | private static final ByteBuffer sCoarseImageData = generateCoarseData(); 57 | private static final ByteBuffer sFineImageData = generateFineData(); 58 | 59 | 60 | /** 61 | * Creates a test texture in the current GL context. 62 | *
63 | * This follows image conventions, so the pixel data at offset zero is intended to appear 64 | * in the top-left corner. Color values for non-opaque alpha will be pre-multiplied. 65 | * 66 | * @return Handle to texture. 67 | */ 68 | public static int createTestTexture(Image which) { 69 | ByteBuffer buf; 70 | switch (which) { 71 | case COARSE: 72 | buf = sCoarseImageData; 73 | break; 74 | case FINE: 75 | buf = sFineImageData; 76 | break; 77 | default: 78 | throw new RuntimeException("unknown image"); 79 | } 80 | return GlUtil.createImageTexture(buf, TEX_SIZE, TEX_SIZE, FORMAT); 81 | } 82 | 83 | /** 84 | * Generates a "coarse" test image. We want to create a 4x4 block pattern with obvious color 85 | * values in the corners, so that we can confirm orientation and coverage. We also 86 | * leave a couple of alpha holes to check that channel. Single pixels are set in two of 87 | * the corners to make it easy to see if we're cutting the texture off at the edge. 88 | *
89 | * Like most image formats, the pixel data begins with the top-left corner, which is 90 | * upside-down relative to OpenGL conventions. The texture coordinates should be flipped 91 | * vertically. Using an asymmetric patterns lets us check that we're doing that right. 92 | *
93 | * Colors use pre-multiplied alpha (so set glBlendFunc appropriately). 94 | * 95 | * @return A direct ByteBuffer with the 8888 RGBA data. 96 | */ 97 | private static ByteBuffer generateCoarseData() { 98 | byte[] buf = new byte[TEX_SIZE * TEX_SIZE * BYTES_PER_PIXEL]; 99 | final int scale = TEX_SIZE / 4; // convert 64x64 --> 4x4 100 | 101 | for (int i = 0; i < buf.length; i += BYTES_PER_PIXEL) { 102 | int texRow = (i / BYTES_PER_PIXEL) / TEX_SIZE; 103 | int texCol = (i / BYTES_PER_PIXEL) % TEX_SIZE; 104 | 105 | int gridRow = texRow / scale; // 0-3 106 | int gridCol = texCol / scale; // 0-3 107 | int gridIndex = (gridRow * 4) + gridCol; // 0-15 108 | 109 | int color = GRID[gridIndex]; 110 | 111 | // override the pixels in two corners to check coverage 112 | if (i == 0) { 113 | color = OPAQUE | WHITE; 114 | } else if (i == buf.length - BYTES_PER_PIXEL) { 115 | color = OPAQUE | WHITE; 116 | } 117 | 118 | // extract RGBA; use "int" instead of "byte" to get unsigned values 119 | int red = color & 0xff; 120 | int green = (color >> 8) & 0xff; 121 | int blue = (color >> 16) & 0xff; 122 | int alpha = (color >> 24) & 0xff; 123 | 124 | // pre-multiply colors and store in buffer 125 | float alphaM = alpha / 255.0f; 126 | buf[i] = (byte) (red * alphaM); 127 | buf[i+1] = (byte) (green * alphaM); 128 | buf[i+2] = (byte) (blue * alphaM); 129 | buf[i+3] = (byte) alpha; 130 | } 131 | 132 | ByteBuffer byteBuf = ByteBuffer.allocateDirect(buf.length); 133 | byteBuf.put(buf); 134 | byteBuf.position(0); 135 | return byteBuf; 136 | } 137 | 138 | /** 139 | * Generates a fine-grained test image. 140 | * 141 | * @return A direct ByteBuffer with the 8888 RGBA data. 142 | */ 143 | private static ByteBuffer generateFineData() { 144 | byte[] buf = new byte[TEX_SIZE * TEX_SIZE * BYTES_PER_PIXEL]; 145 | 146 | // top/left: single-pixel red/blue 147 | checkerPattern(buf, 0, 0, TEX_SIZE / 2, TEX_SIZE / 2, 148 | OPAQUE|RED, OPAQUE|BLUE, 0x01); 149 | // bottom/right: two-pixel red/green 150 | checkerPattern(buf, TEX_SIZE / 2, TEX_SIZE / 2, TEX_SIZE, TEX_SIZE, 151 | OPAQUE|RED, OPAQUE|GREEN, 0x02); 152 | // bottom/left: four-pixel blue/green 153 | checkerPattern(buf, 0, TEX_SIZE / 2, TEX_SIZE / 2, TEX_SIZE, 154 | OPAQUE|BLUE, OPAQUE|GREEN, 0x04); 155 | // top/right: eight-pixel black/white 156 | checkerPattern(buf, TEX_SIZE / 2, 0, TEX_SIZE, TEX_SIZE / 2, 157 | OPAQUE|WHITE, OPAQUE|BLACK, 0x08); 158 | 159 | ByteBuffer byteBuf = ByteBuffer.allocateDirect(buf.length); 160 | byteBuf.put(buf); 161 | byteBuf.position(0); 162 | return byteBuf; 163 | } 164 | 165 | private static void checkerPattern(byte[] buf, int left, int top, int right, int bottom, 166 | int color1, int color2, int bit) { 167 | for (int row = top; row < bottom; row++) { 168 | int rowOffset = row * TEX_SIZE * BYTES_PER_PIXEL; 169 | for (int col = left; col < right; col++) { 170 | int offset = rowOffset + col * BYTES_PER_PIXEL; 171 | int color; 172 | if (((row & bit) ^ (col & bit)) == 0) { 173 | color = color1; 174 | } else { 175 | color = color2; 176 | } 177 | 178 | // extract RGBA; use "int" instead of "byte" to get unsigned values 179 | int red = color & 0xff; 180 | int green = (color >> 8) & 0xff; 181 | int blue = (color >> 16) & 0xff; 182 | int alpha = (color >> 24) & 0xff; 183 | 184 | // pre-multiply colors and store in buffer 185 | float alphaM = alpha / 255.0f; 186 | buf[offset] = (byte) (red * alphaM); 187 | buf[offset+1] = (byte) (green * alphaM); 188 | buf[offset+2] = (byte) (blue * alphaM); 189 | buf[offset+3] = (byte) alpha; 190 | } 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /rtmppublisher/src/main/java/com/takusemba/rtmppublisher/gles/GlUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 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.takusemba.rtmppublisher.gles; 18 | 19 | import android.opengl.GLES20; 20 | import android.opengl.GLES30; 21 | import android.opengl.Matrix; 22 | import android.util.Log; 23 | import java.nio.ByteBuffer; 24 | import java.nio.ByteOrder; 25 | import java.nio.FloatBuffer; 26 | 27 | /** 28 | * Some OpenGL utility functions. 29 | */ 30 | public class GlUtil { 31 | public static final String TAG = "Grafika"; 32 | 33 | /** Identity matrix for general use. Don't modify or life will get weird. */ 34 | public static final float[] IDENTITY_MATRIX; 35 | static { 36 | IDENTITY_MATRIX = new float[16]; 37 | Matrix.setIdentityM(IDENTITY_MATRIX, 0); 38 | } 39 | 40 | private static final int SIZEOF_FLOAT = 4; 41 | 42 | 43 | private GlUtil() {} // do not instantiate 44 | 45 | /** 46 | * Creates a new program from the supplied vertex and fragment shaders. 47 | * 48 | * @return A handle to the program, or 0 on failure. 49 | */ 50 | public static int createProgram(String vertexSource, String fragmentSource) { 51 | int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); 52 | if (vertexShader == 0) { 53 | return 0; 54 | } 55 | int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); 56 | if (pixelShader == 0) { 57 | return 0; 58 | } 59 | 60 | int program = GLES20.glCreateProgram(); 61 | checkGlError("glCreateProgram"); 62 | if (program == 0) { 63 | Log.e(TAG, "Could not create program"); 64 | } 65 | GLES20.glAttachShader(program, vertexShader); 66 | checkGlError("glAttachShader"); 67 | GLES20.glAttachShader(program, pixelShader); 68 | checkGlError("glAttachShader"); 69 | GLES20.glLinkProgram(program); 70 | int[] linkStatus = new int[1]; 71 | GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); 72 | if (linkStatus[0] != GLES20.GL_TRUE) { 73 | Log.e(TAG, "Could not link program: "); 74 | Log.e(TAG, GLES20.glGetProgramInfoLog(program)); 75 | GLES20.glDeleteProgram(program); 76 | program = 0; 77 | } 78 | return program; 79 | } 80 | 81 | /** 82 | * Compiles the provided shader source. 83 | * 84 | * @return A handle to the shader, or 0 on failure. 85 | */ 86 | public static int loadShader(int shaderType, String source) { 87 | int shader = GLES20.glCreateShader(shaderType); 88 | checkGlError("glCreateShader type=" + shaderType); 89 | GLES20.glShaderSource(shader, source); 90 | GLES20.glCompileShader(shader); 91 | int[] compiled = new int[1]; 92 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); 93 | if (compiled[0] == 0) { 94 | Log.e(TAG, "Could not compile shader " + shaderType + ":"); 95 | Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader)); 96 | GLES20.glDeleteShader(shader); 97 | shader = 0; 98 | } 99 | return shader; 100 | } 101 | 102 | /** 103 | * Checks to see if a GLES error has been raised. 104 | */ 105 | public static void checkGlError(String op) { 106 | int error = GLES20.glGetError(); 107 | if (error != GLES20.GL_NO_ERROR) { 108 | String msg = op + ": glError 0x" + Integer.toHexString(error); 109 | Log.e(TAG, msg); 110 | throw new RuntimeException(msg); 111 | } 112 | } 113 | 114 | /** 115 | * Checks to see if the location we obtained is valid. GLES returns -1 if a label 116 | * could not be found, but does not set the GL error. 117 | *
118 | * Throws a RuntimeException if the location is invalid. 119 | */ 120 | public static void checkLocation(int location, String label) { 121 | if (location < 0) { 122 | throw new RuntimeException("Unable to locate '" + label + "' in program"); 123 | } 124 | } 125 | 126 | /** 127 | * Creates a texture from raw data. 128 | * 129 | * @param data Image data, in a "direct" ByteBuffer. 130 | * @param width Texture width, in pixels (not bytes). 131 | * @param height Texture height, in pixels. 132 | * @param format Image data format (use constant appropriate for glTexImage2D(), e.g. GL_RGBA). 133 | * @return Handle to texture. 134 | */ 135 | public static int createImageTexture(ByteBuffer data, int width, int height, int format) { 136 | int[] textureHandles = new int[1]; 137 | int textureHandle; 138 | 139 | GLES20.glGenTextures(1, textureHandles, 0); 140 | textureHandle = textureHandles[0]; 141 | GlUtil.checkGlError("glGenTextures"); 142 | 143 | // Bind the texture handle to the 2D texture target. 144 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle); 145 | 146 | // Configure min/mag filtering, i.e. what scaling method do we use if what we're rendering 147 | // is smaller or larger than the source image. 148 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, 149 | GLES20.GL_LINEAR); 150 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, 151 | GLES20.GL_LINEAR); 152 | GlUtil.checkGlError("loadImageTexture"); 153 | 154 | // Load the data from the buffer into the texture handle. 155 | GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, /*level*/ 0, format, 156 | width, height, /*border*/ 0, format, GLES20.GL_UNSIGNED_BYTE, data); 157 | GlUtil.checkGlError("loadImageTexture"); 158 | 159 | return textureHandle; 160 | } 161 | 162 | /** 163 | * Allocates a direct float buffer, and populates it with the float array data. 164 | */ 165 | public static FloatBuffer createFloatBuffer(float[] coords) { 166 | // Allocate a direct ByteBuffer, using 4 bytes per float, and copy coords into it. 167 | ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * SIZEOF_FLOAT); 168 | bb.order(ByteOrder.nativeOrder()); 169 | FloatBuffer fb = bb.asFloatBuffer(); 170 | fb.put(coords); 171 | fb.position(0); 172 | return fb; 173 | } 174 | 175 | /** 176 | * Writes GL version info to the log. 177 | */ 178 | public static void logVersionInfo() { 179 | Log.i(TAG, "vendor : " + GLES20.glGetString(GLES20.GL_VENDOR)); 180 | Log.i(TAG, "renderer: " + GLES20.glGetString(GLES20.GL_RENDERER)); 181 | Log.i(TAG, "version : " + GLES20.glGetString(GLES20.GL_VERSION)); 182 | 183 | if (false) { 184 | int[] values = new int[1]; 185 | GLES30.glGetIntegerv(GLES30.GL_MAJOR_VERSION, values, 0); 186 | int majorVersion = values[0]; 187 | GLES30.glGetIntegerv(GLES30.GL_MINOR_VERSION, values, 0); 188 | int minorVersion = values[0]; 189 | if (GLES30.glGetError() == GLES30.GL_NO_ERROR) { 190 | Log.i(TAG, "iversion: " + majorVersion + "." + minorVersion); 191 | } 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /rtmppublisher/src/main/java/com/takusemba/rtmppublisher/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.takusemba.rtmppublisher.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 | -------------------------------------------------------------------------------- /rtmppublisher/src/main/java/com/takusemba/rtmppublisher/gles/Sprite2d.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 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.takusemba.rtmppublisher.gles; 18 | 19 | import android.opengl.Matrix; 20 | 21 | /** 22 | * Base class for a 2d object. Includes position, scale, rotation, and flat-shaded color. 23 | */ 24 | public class Sprite2d { 25 | private static final String TAG = GlUtil.TAG; 26 | 27 | private Drawable2d mDrawable; 28 | private float mColor[]; 29 | private int mTextureId; 30 | private float mAngle; 31 | private float mScaleX, mScaleY; 32 | private float mPosX, mPosY; 33 | 34 | private float[] mModelViewMatrix; 35 | private boolean mMatrixReady; 36 | 37 | private float[] mScratchMatrix = new float[16]; 38 | 39 | public Sprite2d(Drawable2d drawable) { 40 | mDrawable = drawable; 41 | mColor = new float[4]; 42 | mColor[3] = 1.0f; 43 | mTextureId = -1; 44 | 45 | mModelViewMatrix = new float[16]; 46 | mMatrixReady = false; 47 | } 48 | 49 | /** 50 | * Re-computes mModelViewMatrix, based on the current values for rotation, scale, and 51 | * translation. 52 | */ 53 | private void recomputeMatrix() { 54 | float[] modelView = mModelViewMatrix; 55 | 56 | Matrix.setIdentityM(modelView, 0); 57 | Matrix.translateM(modelView, 0, mPosX, mPosY, 0.0f); 58 | if (mAngle != 0.0f) { 59 | Matrix.rotateM(modelView, 0, mAngle, 0.0f, 0.0f, 1.0f); 60 | } 61 | Matrix.scaleM(modelView, 0, mScaleX, mScaleY, 1.0f); 62 | mMatrixReady = true; 63 | } 64 | 65 | /** 66 | * Returns the sprite scale along the X axis. 67 | */ 68 | public float getScaleX() { 69 | return mScaleX; 70 | } 71 | 72 | /** 73 | * Returns the sprite scale along the Y axis. 74 | */ 75 | public float getScaleY() { 76 | return mScaleY; 77 | } 78 | 79 | /** 80 | * Sets the sprite scale (size). 81 | */ 82 | public void setScale(float scaleX, float scaleY) { 83 | mScaleX = scaleX; 84 | mScaleY = scaleY; 85 | mMatrixReady = false; 86 | } 87 | 88 | /** 89 | * Gets the sprite rotation angle, in degrees. 90 | */ 91 | public float getRotation() { 92 | return mAngle; 93 | } 94 | 95 | /** 96 | * Sets the sprite rotation angle, in degrees. Sprite will rotate counter-clockwise. 97 | */ 98 | public void setRotation(float angle) { 99 | // Normalize. We're not expecting it to be way off, so just iterate. 100 | while (angle >= 360.0f) { 101 | angle -= 360.0f; 102 | } 103 | while (angle <= -360.0f) { 104 | angle += 360.0f; 105 | } 106 | mAngle = angle; 107 | mMatrixReady = false; 108 | } 109 | 110 | /** 111 | * Returns the position on the X axis. 112 | */ 113 | public float getPositionX() { 114 | return mPosX; 115 | } 116 | 117 | /** 118 | * Returns the position on the Y axis. 119 | */ 120 | public float getPositionY() { 121 | return mPosY; 122 | } 123 | 124 | /** 125 | * Sets the sprite position. 126 | */ 127 | public void setPosition(float posX, float posY) { 128 | mPosX = posX; 129 | mPosY = posY; 130 | mMatrixReady = false; 131 | } 132 | 133 | /** 134 | * Returns the model-view matrix. 135 | *
136 | * To avoid allocations, this returns internal state. The caller must not modify it. 137 | */ 138 | public float[] getModelViewMatrix() { 139 | if (!mMatrixReady) { 140 | recomputeMatrix(); 141 | } 142 | return mModelViewMatrix; 143 | } 144 | 145 | /** 146 | * Sets color to use for flat-shaded rendering. Has no effect on textured rendering. 147 | */ 148 | public void setColor(float red, float green, float blue) { 149 | mColor[0] = red; 150 | mColor[1] = green; 151 | mColor[2] = blue; 152 | } 153 | 154 | /** 155 | * Sets texture to use for textured rendering. Has no effect on flat-shaded rendering. 156 | */ 157 | public void setTexture(int textureId) { 158 | mTextureId = textureId; 159 | } 160 | 161 | /** 162 | * Returns the color. 163 | *
164 | * To avoid allocations, this returns internal state. The caller must not modify it. 165 | */ 166 | public float[] getColor() { 167 | return mColor; 168 | } 169 | 170 | /** 171 | * Draws the rectangle with the supplied program and projection matrix. 172 | */ 173 | public void draw(FlatShadedProgram program, float[] projectionMatrix) { 174 | // Compute model/view/projection matrix. 175 | Matrix.multiplyMM(mScratchMatrix, 0, projectionMatrix, 0, getModelViewMatrix(), 0); 176 | 177 | program.draw(mScratchMatrix, mColor, mDrawable.getVertexArray(), 0, 178 | mDrawable.getVertexCount(), mDrawable.getCoordsPerVertex(), 179 | mDrawable.getVertexStride()); 180 | } 181 | 182 | /** 183 | * Draws the rectangle with the supplied program and projection matrix. 184 | */ 185 | public void draw(Texture2dProgram program, float[] projectionMatrix) { 186 | // Compute model/view/projection matrix. 187 | Matrix.multiplyMM(mScratchMatrix, 0, projectionMatrix, 0, getModelViewMatrix(), 0); 188 | 189 | program.draw(mScratchMatrix, mDrawable.getVertexArray(), 0, 190 | mDrawable.getVertexCount(), mDrawable.getCoordsPerVertex(), 191 | mDrawable.getVertexStride(), GlUtil.IDENTITY_MATRIX, mDrawable.getTexCoordArray(), 192 | mTextureId, mDrawable.getTexCoordStride()); 193 | } 194 | 195 | @Override 196 | public String toString() { 197 | return "[Sprite2d pos=" + mPosX + "," + mPosY + 198 | " scale=" + mScaleX + "," + mScaleY + " angle=" + mAngle + 199 | " color={" + mColor[0] + "," + mColor[1] + "," + mColor[2] + 200 | "} drawable=" + mDrawable + "]"; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /rtmppublisher/src/main/java/com/takusemba/rtmppublisher/gles/Texture2dProgram.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 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.takusemba.rtmppublisher.gles; 18 | 19 | import android.opengl.GLES11Ext; 20 | import android.opengl.GLES20; 21 | import android.util.Log; 22 | import java.nio.FloatBuffer; 23 | 24 | /** 25 | * GL program and supporting functions for textured 2D shapes. 26 | */ 27 | public class Texture2dProgram { 28 | private static final String TAG = GlUtil.TAG; 29 | 30 | public enum ProgramType { 31 | TEXTURE_2D, TEXTURE_EXT, TEXTURE_EXT_BW, TEXTURE_EXT_FILT 32 | } 33 | 34 | // Simple vertex shader, used for all programs. 35 | private static final String VERTEX_SHADER = 36 | "uniform mat4 uMVPMatrix;\n" + 37 | "uniform mat4 uTexMatrix;\n" + 38 | "attribute vec4 aPosition;\n" + 39 | "attribute vec4 aTextureCoord;\n" + 40 | "varying vec2 vTextureCoord;\n" + 41 | "void main() {\n" + 42 | " gl_Position = uMVPMatrix * aPosition;\n" + 43 | " vTextureCoord = (uTexMatrix * aTextureCoord).xy;\n" + 44 | "}\n"; 45 | 46 | // Simple fragment shader for use with "normal" 2D textures. 47 | private static final String FRAGMENT_SHADER_2D = 48 | "precision mediump float;\n" + 49 | "varying vec2 vTextureCoord;\n" + 50 | "uniform sampler2D sTexture;\n" + 51 | "void main() {\n" + 52 | " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + 53 | "}\n"; 54 | 55 | // Simple fragment shader for use with external 2D textures (e.g. what we get from 56 | // SurfaceTexture). 57 | private static final String FRAGMENT_SHADER_EXT = 58 | "#extension GL_OES_EGL_image_external : require\n" + 59 | "precision mediump float;\n" + 60 | "varying vec2 vTextureCoord;\n" + 61 | "uniform samplerExternalOES sTexture;\n" + 62 | "void main() {\n" + 63 | " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + 64 | "}\n"; 65 | 66 | // Fragment shader that converts color to black & white with a simple transformation. 67 | private static final String FRAGMENT_SHADER_EXT_BW = 68 | "#extension GL_OES_EGL_image_external : require\n" + 69 | "precision mediump float;\n" + 70 | "varying vec2 vTextureCoord;\n" + 71 | "uniform samplerExternalOES sTexture;\n" + 72 | "void main() {\n" + 73 | " vec4 tc = texture2D(sTexture, vTextureCoord);\n" + 74 | " float color = tc.r * 0.3 + tc.g * 0.59 + tc.b * 0.11;\n" + 75 | " gl_FragColor = vec4(color, color, color, 1.0);\n" + 76 | "}\n"; 77 | 78 | // Fragment shader with a convolution filter. The upper-left half will be drawn normally, 79 | // the lower-right half will have the filter applied, and a thin red line will be drawn 80 | // at the border. 81 | // 82 | // This is not optimized for performance. Some things that might make this faster: 83 | // - Remove the conditionals. They're used to present a half & half view with a red 84 | // stripe across the middle, but that's only useful for a demo. 85 | // - Unroll the loop. Ideally the compiler does this for you when it's beneficial. 86 | // - Bake the filter kernel into the shader, instead of passing it through a uniform 87 | // array. That, combined with loop unrolling, should reduce memory accesses. 88 | public static final int KERNEL_SIZE = 9; 89 | private static final String FRAGMENT_SHADER_EXT_FILT = 90 | "#extension GL_OES_EGL_image_external : require\n" + 91 | "#define KERNEL_SIZE " + KERNEL_SIZE + "\n" + 92 | "precision highp float;\n" + 93 | "varying vec2 vTextureCoord;\n" + 94 | "uniform samplerExternalOES sTexture;\n" + 95 | "uniform float uKernel[KERNEL_SIZE];\n" + 96 | "uniform vec2 uTexOffset[KERNEL_SIZE];\n" + 97 | "uniform float uColorAdjust;\n" + 98 | "void main() {\n" + 99 | " int i = 0;\n" + 100 | " vec4 sum = vec4(0.0);\n" + 101 | " if (vTextureCoord.x < vTextureCoord.y - 0.005) {\n" + 102 | " for (i = 0; i < KERNEL_SIZE; i++) {\n" + 103 | " vec4 texc = texture2D(sTexture, vTextureCoord + uTexOffset[i]);\n" + 104 | " sum += texc * uKernel[i];\n" + 105 | " }\n" + 106 | " sum += uColorAdjust;\n" + 107 | " } else if (vTextureCoord.x > vTextureCoord.y + 0.005) {\n" + 108 | " sum = texture2D(sTexture, vTextureCoord);\n" + 109 | " } else {\n" + 110 | " sum.r = 1.0;\n" + 111 | " }\n" + 112 | " gl_FragColor = sum;\n" + 113 | "}\n"; 114 | 115 | private ProgramType mProgramType; 116 | 117 | // Handles to the GL program and various components of it. 118 | private int mProgramHandle; 119 | private int muMVPMatrixLoc; 120 | private int muTexMatrixLoc; 121 | private int muKernelLoc; 122 | private int muTexOffsetLoc; 123 | private int muColorAdjustLoc; 124 | private int maPositionLoc; 125 | private int maTextureCoordLoc; 126 | 127 | private int mTextureTarget; 128 | 129 | private float[] mKernel = new float[KERNEL_SIZE]; 130 | private float[] mTexOffset; 131 | private float mColorAdjust; 132 | 133 | 134 | /** 135 | * Prepares the program in the current EGL context. 136 | */ 137 | public Texture2dProgram(ProgramType programType) { 138 | mProgramType = programType; 139 | 140 | switch (programType) { 141 | case TEXTURE_2D: 142 | mTextureTarget = GLES20.GL_TEXTURE_2D; 143 | mProgramHandle = GlUtil.createProgram(VERTEX_SHADER, FRAGMENT_SHADER_2D); 144 | break; 145 | case TEXTURE_EXT: 146 | mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; 147 | mProgramHandle = GlUtil.createProgram(VERTEX_SHADER, FRAGMENT_SHADER_EXT); 148 | break; 149 | case TEXTURE_EXT_BW: 150 | mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; 151 | mProgramHandle = GlUtil.createProgram(VERTEX_SHADER, FRAGMENT_SHADER_EXT_BW); 152 | break; 153 | case TEXTURE_EXT_FILT: 154 | mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; 155 | mProgramHandle = GlUtil.createProgram(VERTEX_SHADER, FRAGMENT_SHADER_EXT_FILT); 156 | break; 157 | default: 158 | throw new RuntimeException("Unhandled type " + programType); 159 | } 160 | if (mProgramHandle == 0) { 161 | throw new RuntimeException("Unable to create program"); 162 | } 163 | Log.d(TAG, "Created program " + mProgramHandle + " (" + programType + ")"); 164 | 165 | // get locations of attributes and uniforms 166 | 167 | maPositionLoc = GLES20.glGetAttribLocation(mProgramHandle, "aPosition"); 168 | GlUtil.checkLocation(maPositionLoc, "aPosition"); 169 | maTextureCoordLoc = GLES20.glGetAttribLocation(mProgramHandle, "aTextureCoord"); 170 | GlUtil.checkLocation(maTextureCoordLoc, "aTextureCoord"); 171 | muMVPMatrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "uMVPMatrix"); 172 | GlUtil.checkLocation(muMVPMatrixLoc, "uMVPMatrix"); 173 | muTexMatrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "uTexMatrix"); 174 | GlUtil.checkLocation(muTexMatrixLoc, "uTexMatrix"); 175 | muKernelLoc = GLES20.glGetUniformLocation(mProgramHandle, "uKernel"); 176 | if (muKernelLoc < 0) { 177 | // no kernel in this one 178 | muKernelLoc = -1; 179 | muTexOffsetLoc = -1; 180 | muColorAdjustLoc = -1; 181 | } else { 182 | // has kernel, must also have tex offset and color adj 183 | muTexOffsetLoc = GLES20.glGetUniformLocation(mProgramHandle, "uTexOffset"); 184 | GlUtil.checkLocation(muTexOffsetLoc, "uTexOffset"); 185 | muColorAdjustLoc = GLES20.glGetUniformLocation(mProgramHandle, "uColorAdjust"); 186 | GlUtil.checkLocation(muColorAdjustLoc, "uColorAdjust"); 187 | 188 | // initialize default values 189 | setKernel(new float[] {0f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f}, 0f); 190 | setTexSize(256, 256); 191 | } 192 | } 193 | 194 | /** 195 | * Releases the program. 196 | *
197 | * The appropriate EGL context must be current (i.e. the one that was used to create 198 | * the program). 199 | */ 200 | public void release() { 201 | Log.d(TAG, "deleting program " + mProgramHandle); 202 | GLES20.glDeleteProgram(mProgramHandle); 203 | mProgramHandle = -1; 204 | } 205 | 206 | /** 207 | * Returns the program type. 208 | */ 209 | public ProgramType getProgramType() { 210 | return mProgramType; 211 | } 212 | 213 | /** 214 | * Creates a texture object suitable for use with this program. 215 | *
216 | * On exit, the texture will be bound. 217 | */ 218 | public int createTextureObject() { 219 | int[] textures = new int[1]; 220 | GLES20.glGenTextures(1, textures, 0); 221 | GlUtil.checkGlError("glGenTextures"); 222 | 223 | int texId = textures[0]; 224 | GLES20.glBindTexture(mTextureTarget, texId); 225 | GlUtil.checkGlError("glBindTexture " + texId); 226 | 227 | GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, 228 | GLES20.GL_NEAREST); 229 | GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, 230 | GLES20.GL_LINEAR); 231 | GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, 232 | GLES20.GL_CLAMP_TO_EDGE); 233 | GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, 234 | GLES20.GL_CLAMP_TO_EDGE); 235 | GlUtil.checkGlError("glTexParameter"); 236 | 237 | return texId; 238 | } 239 | 240 | /** 241 | * Configures the convolution filter values. 242 | * 243 | * @param values Normalized filter values; must be KERNEL_SIZE elements. 244 | */ 245 | public void setKernel(float[] values, float colorAdj) { 246 | if (values.length != KERNEL_SIZE) { 247 | throw new IllegalArgumentException("Kernel size is " + values.length + 248 | " vs. " + KERNEL_SIZE); 249 | } 250 | System.arraycopy(values, 0, mKernel, 0, KERNEL_SIZE); 251 | mColorAdjust = colorAdj; 252 | //Log.d(TAG, "filt kernel: " + Arrays.toString(mKernel) + ", adj=" + colorAdj); 253 | } 254 | 255 | /** 256 | * Sets the size of the texture. This is used to find adjacent texels when filtering. 257 | */ 258 | public void setTexSize(int width, int height) { 259 | float rw = 1.0f / width; 260 | float rh = 1.0f / height; 261 | 262 | // Don't need to create a new array here, but it's syntactically convenient. 263 | mTexOffset = new float[] { 264 | -rw, -rh, 0f, -rh, rw, -rh, 265 | -rw, 0f, 0f, 0f, rw, 0f, 266 | -rw, rh, 0f, rh, rw, rh 267 | }; 268 | //Log.d(TAG, "filt size: " + width + "x" + height + ": " + Arrays.toString(mTexOffset)); 269 | } 270 | 271 | /** 272 | * Issues the draw call. Does the full setup on every call. 273 | * 274 | * @param mvpMatrix The 4x4 projection matrix. 275 | * @param vertexBuffer Buffer with vertex position data. 276 | * @param firstVertex Index of first vertex to use in vertexBuffer. 277 | * @param vertexCount Number of vertices in vertexBuffer. 278 | * @param coordsPerVertex The number of coordinates per vertex (e.g. x,y is 2). 279 | * @param vertexStride Width, in bytes, of the position data for each vertex (often 280 | * vertexCount * sizeof(float)). 281 | * @param texMatrix A 4x4 transformation matrix for texture coords. (Primarily intended 282 | * for use with SurfaceTexture.) 283 | * @param texBuffer Buffer with vertex texture data. 284 | * @param texStride Width, in bytes, of the texture data for each vertex. 285 | */ 286 | public void draw(float[] mvpMatrix, FloatBuffer vertexBuffer, int firstVertex, 287 | int vertexCount, int coordsPerVertex, int vertexStride, 288 | float[] texMatrix, FloatBuffer texBuffer, int textureId, int texStride) { 289 | GlUtil.checkGlError("draw start"); 290 | 291 | // Select the program. 292 | GLES20.glUseProgram(mProgramHandle); 293 | GlUtil.checkGlError("glUseProgram"); 294 | 295 | // Set the texture. 296 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 297 | GLES20.glBindTexture(mTextureTarget, textureId); 298 | 299 | // Copy the model / view / projection matrix over. 300 | GLES20.glUniformMatrix4fv(muMVPMatrixLoc, 1, false, mvpMatrix, 0); 301 | GlUtil.checkGlError("glUniformMatrix4fv"); 302 | 303 | // Copy the texture transformation matrix over. 304 | GLES20.glUniformMatrix4fv(muTexMatrixLoc, 1, false, texMatrix, 0); 305 | GlUtil.checkGlError("glUniformMatrix4fv"); 306 | 307 | // Enable the "aPosition" vertex attribute. 308 | GLES20.glEnableVertexAttribArray(maPositionLoc); 309 | GlUtil.checkGlError("glEnableVertexAttribArray"); 310 | 311 | // Connect vertexBuffer to "aPosition". 312 | GLES20.glVertexAttribPointer(maPositionLoc, coordsPerVertex, 313 | GLES20.GL_FLOAT, false, vertexStride, vertexBuffer); 314 | GlUtil.checkGlError("glVertexAttribPointer"); 315 | 316 | // Enable the "aTextureCoord" vertex attribute. 317 | GLES20.glEnableVertexAttribArray(maTextureCoordLoc); 318 | GlUtil.checkGlError("glEnableVertexAttribArray"); 319 | 320 | // Connect texBuffer to "aTextureCoord". 321 | GLES20.glVertexAttribPointer(maTextureCoordLoc, 2, 322 | GLES20.GL_FLOAT, false, texStride, texBuffer); 323 | GlUtil.checkGlError("glVertexAttribPointer"); 324 | 325 | // Populate the convolution kernel, if present. 326 | if (muKernelLoc >= 0) { 327 | GLES20.glUniform1fv(muKernelLoc, KERNEL_SIZE, mKernel, 0); 328 | GLES20.glUniform2fv(muTexOffsetLoc, KERNEL_SIZE, mTexOffset, 0); 329 | GLES20.glUniform1f(muColorAdjustLoc, mColorAdjust); 330 | } 331 | 332 | // Draw the rect. 333 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, firstVertex, vertexCount); 334 | GlUtil.checkGlError("glDrawArrays"); 335 | 336 | // Done -- disable vertex array, texture, and program. 337 | GLES20.glDisableVertexAttribArray(maPositionLoc); 338 | GLES20.glDisableVertexAttribArray(maTextureCoordLoc); 339 | GLES20.glBindTexture(mTextureTarget, 0); 340 | GLES20.glUseProgram(0); 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /rtmppublisher/src/main/java/com/takusemba/rtmppublisher/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.takusemba.rtmppublisher.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 |
--------------------------------------------------------------------------------
/rtmppublisher/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |