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