Home | History | Annotate | Download | only in sensors
      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      */
     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      */
     84     public CtsMediaOutputSurface() {
     85         setup(this);
     86     }
     87 
     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      */
     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      */
    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      */
    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      */
    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      */
    218     public Surface getSurface() {
    219         return mSurface;
    220     }
    221 
    222     /**
    223      * Replaces the fragment shader.
    224      */
    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      */
    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      */
    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      */
    292     public void drawImage() {
    293         mTextureRender.drawFrame(mSurfaceTexture);
    294     }
    295 
    296     public void latchImage() {
    297         mTextureRender.checkGlError("before updateTexImage");
    298         mSurfaceTexture.updateTexImage();
    299     }
    300 
    301     @Override
    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      */
    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