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