1 /* 2 * Copyright (C) 2013 The Android Open Source Project 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 android.media.cts; 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.EGLSurface; 25 import android.util.Log; 26 import android.view.Surface; 27 28 29 /** 30 * Holds state associated with a Surface used for MediaCodec decoder output. 31 * <p> 32 * The (width,height) constructor for this class will prepare GL, create a SurfaceTexture, 33 * and then create a Surface for that SurfaceTexture. The Surface can be passed to 34 * MediaCodec.configure() to receive decoder output. When a frame arrives, we latch the 35 * texture with updateTexImage, then render the texture with GL to a pbuffer. 36 * <p> 37 * The no-arg constructor skips the GL preparation step and doesn't allocate a pbuffer. 38 * Instead, it just creates the Surface and SurfaceTexture, and when a frame arrives 39 * we just draw it on whatever surface is current. 40 * <p> 41 * By default, the Surface will be using a BufferQueue in asynchronous mode, so we 42 * can potentially drop frames. 43 */ 44 class OutputSurface implements SurfaceTexture.OnFrameAvailableListener { 45 private static final String TAG = "OutputSurface"; 46 private static final boolean VERBOSE = false; 47 48 private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY; 49 private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT; 50 private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; 51 52 private SurfaceTexture mSurfaceTexture; 53 private Surface mSurface; 54 55 private Object mFrameSyncObject = new Object(); // guards mFrameAvailable 56 private boolean mFrameAvailable; 57 58 private TextureRender mTextureRender; 59 60 /** 61 * Creates an OutputSurface backed by a pbuffer with the specifed dimensions. The new 62 * EGL context and surface will be made current. Creates a Surface that can be passed 63 * to MediaCodec.configure(). 64 */ 65 public OutputSurface(int width, int height) { 66 if (width <= 0 || height <= 0) { 67 throw new IllegalArgumentException(); 68 } 69 70 eglSetup(width, height); 71 makeCurrent(); 72 73 setup(this); 74 } 75 76 /** 77 * Creates an OutputSurface using the current EGL context (rather than establishing a 78 * new one). Creates a Surface that can be passed to MediaCodec.configure(). 79 */ 80 public OutputSurface() { 81 setup(this); 82 } 83 84 public OutputSurface(final SurfaceTexture.OnFrameAvailableListener listener) { 85 setup(listener); 86 } 87 88 /** 89 * Creates instances of TextureRender and SurfaceTexture, and a Surface associated 90 * with the SurfaceTexture. 91 */ 92 private void setup(SurfaceTexture.OnFrameAvailableListener listener) { 93 mTextureRender = new TextureRender(); 94 mTextureRender.surfaceCreated(); 95 96 // Even if we don't access the SurfaceTexture after the constructor returns, we 97 // still need to keep a reference to it. The Surface doesn't retain a reference 98 // at the Java level, so if we don't either then the object can get GCed, which 99 // causes the native finalizer to run. 100 if (VERBOSE) Log.d(TAG, "textureID=" + mTextureRender.getTextureId()); 101 mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId()); 102 103 // This doesn't work if OutputSurface is created on the thread that CTS started for 104 // these test cases. 105 // 106 // The CTS-created thread has a Looper, and the SurfaceTexture constructor will 107 // create a Handler that uses it. The "frame available" message is delivered 108 // there, but since we're not a Looper-based thread we'll never see it. For 109 // this to do anything useful, OutputSurface must be created on a thread without 110 // a Looper, so that SurfaceTexture uses the main application Looper instead. 111 // 112 // Java language note: passing "this" out of a constructor is generally unwise, 113 // but we should be able to get away with it here. 114 mSurfaceTexture.setOnFrameAvailableListener(listener); 115 116 mSurface = new Surface(mSurfaceTexture); 117 } 118 119 /** 120 * Prepares EGL. We want a GLES 2.0 context and a surface that supports pbuffer. 121 */ 122 private void eglSetup(int width, int height) { 123 mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); 124 if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { 125 throw new RuntimeException("unable to get EGL14 display"); 126 } 127 int[] version = new int[2]; 128 if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { 129 mEGLDisplay = null; 130 throw new RuntimeException("unable to initialize EGL14"); 131 } 132 133 // Configure EGL for pbuffer and OpenGL ES 2.0. We want enough RGB bits 134 // to be able to tell if the frame is reasonable. 135 int[] attribList = { 136 EGL14.EGL_RED_SIZE, 8, 137 EGL14.EGL_GREEN_SIZE, 8, 138 EGL14.EGL_BLUE_SIZE, 8, 139 EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, 140 EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT, 141 EGL14.EGL_NONE 142 }; 143 EGLConfig[] configs = new EGLConfig[1]; 144 int[] numConfigs = new int[1]; 145 if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length, 146 numConfigs, 0)) { 147 throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config"); 148 } 149 150 // Configure context for OpenGL ES 2.0. 151 int[] attrib_list = { 152 EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, 153 EGL14.EGL_NONE 154 }; 155 mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT, 156 attrib_list, 0); 157 checkEglError("eglCreateContext"); 158 if (mEGLContext == null) { 159 throw new RuntimeException("null context"); 160 } 161 162 // Create a pbuffer surface. By using this for output, we can use glReadPixels 163 // to test values in the output. 164 int[] surfaceAttribs = { 165 EGL14.EGL_WIDTH, width, 166 EGL14.EGL_HEIGHT, height, 167 EGL14.EGL_NONE 168 }; 169 mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs, 0); 170 checkEglError("eglCreatePbufferSurface"); 171 if (mEGLSurface == null) { 172 throw new RuntimeException("surface was null"); 173 } 174 } 175 176 /** 177 * Discard all resources held by this class, notably the EGL context. 178 */ 179 public void release() { 180 if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { 181 EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface); 182 EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); 183 EGL14.eglReleaseThread(); 184 EGL14.eglTerminate(mEGLDisplay); 185 } 186 187 mSurface.release(); 188 189 // this causes a bunch of warnings that appear harmless but might confuse someone: 190 // W BufferQueue: [unnamed-3997-2] cancelBuffer: BufferQueue has been abandoned! 191 //mSurfaceTexture.release(); 192 193 mEGLDisplay = EGL14.EGL_NO_DISPLAY; 194 mEGLContext = EGL14.EGL_NO_CONTEXT; 195 mEGLSurface = EGL14.EGL_NO_SURFACE; 196 197 mTextureRender = null; 198 mSurface = null; 199 mSurfaceTexture = null; 200 } 201 202 /** 203 * Makes our EGL context and surface current. 204 */ 205 public void makeCurrent() { 206 if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { 207 throw new RuntimeException("eglMakeCurrent failed"); 208 } 209 } 210 211 /** 212 * Returns the Surface that we draw onto. 213 */ 214 public Surface getSurface() { 215 return mSurface; 216 } 217 218 /** 219 * Replaces the fragment shader. 220 */ 221 public void changeFragmentShader(String fragmentShader) { 222 mTextureRender.changeFragmentShader(fragmentShader); 223 } 224 225 /** 226 * Latches the next buffer into the texture. Must be called from the thread that created 227 * the OutputSurface object, after the onFrameAvailable callback has signaled that new 228 * data is available. 229 */ 230 public void awaitNewImage() { 231 final int TIMEOUT_MS = 500; 232 233 synchronized (mFrameSyncObject) { 234 while (!mFrameAvailable) { 235 try { 236 // Wait for onFrameAvailable() to signal us. Use a timeout to avoid 237 // stalling the test if it doesn't arrive. 238 mFrameSyncObject.wait(TIMEOUT_MS); 239 if (!mFrameAvailable) { 240 // TODO: if "spurious wakeup", continue while loop 241 throw new RuntimeException("Surface frame wait timed out"); 242 } 243 } catch (InterruptedException ie) { 244 // shouldn't happen 245 throw new RuntimeException(ie); 246 } 247 } 248 mFrameAvailable = false; 249 } 250 251 // Latch the data. 252 mTextureRender.checkGlError("before updateTexImage"); 253 mSurfaceTexture.updateTexImage(); 254 } 255 256 /** 257 * Wait up to given timeout until new image become available. 258 * @param timeoutMs 259 * @return true if new image is available. false for no new image until timeout. 260 */ 261 public boolean checkForNewImage(int timeoutMs) { 262 synchronized (mFrameSyncObject) { 263 while (!mFrameAvailable) { 264 try { 265 // Wait for onFrameAvailable() to signal us. Use a timeout to avoid 266 // stalling the test if it doesn't arrive. 267 mFrameSyncObject.wait(timeoutMs); 268 if (!mFrameAvailable) { 269 return false; 270 } 271 } catch (InterruptedException ie) { 272 // shouldn't happen 273 throw new RuntimeException(ie); 274 } 275 } 276 mFrameAvailable = false; 277 } 278 279 // Latch the data. 280 mTextureRender.checkGlError("before updateTexImage"); 281 mSurfaceTexture.updateTexImage(); 282 return true; 283 } 284 285 /** 286 * Draws the data from SurfaceTexture onto the current EGL surface. 287 */ 288 public void drawImage() { 289 mTextureRender.drawFrame(mSurfaceTexture); 290 } 291 292 public void latchImage() { 293 mTextureRender.checkGlError("before updateTexImage"); 294 mSurfaceTexture.updateTexImage(); 295 } 296 297 @Override 298 public void onFrameAvailable(SurfaceTexture st) { 299 if (VERBOSE) Log.d(TAG, "new frame available"); 300 synchronized (mFrameSyncObject) { 301 if (mFrameAvailable) { 302 throw new RuntimeException("mFrameAvailable already set, frame could be dropped"); 303 } 304 mFrameAvailable = true; 305 mFrameSyncObject.notifyAll(); 306 } 307 } 308 309 /** 310 * Checks for EGL errors. 311 */ 312 private void checkEglError(String msg) { 313 int error; 314 if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { 315 throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error)); 316 } 317 } 318 } 319