Home | History | Annotate | Download | only in cts
      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