/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // from: https://android.googlesource.com/platform/cts/+/lollipop-release/tests/tests/media/src/android/media/cts/OutputSurface.java // blob: fc8ad9cd390c5c311f015d3b7c1359e4d295bc52 // modified: change TIMEOUT_MS from 500 to 10000 package net.ypresto.androidtranscoder.engine; import android.graphics.SurfaceTexture; import android.opengl.EGL14; import android.opengl.EGLConfig; import android.opengl.EGLContext; import android.opengl.EGLDisplay; import android.opengl.EGLSurface; import android.util.Log; import android.view.Surface; /** * Holds state associated with a Surface used for MediaCodec decoder output. *

* The (width,height) constructor for this class will prepare GL, create a SurfaceTexture, * and then create a Surface for that SurfaceTexture. The Surface can be passed to * MediaCodec.configure() to receive decoder output. When a frame arrives, we latch the * texture with updateTexImage, then render the texture with GL to a pbuffer. *

* The no-arg constructor skips the GL preparation step and doesn't allocate a pbuffer. * Instead, it just creates the Surface and SurfaceTexture, and when a frame arrives * we just draw it on whatever surface is current. *

* By default, the Surface will be using a BufferQueue in asynchronous mode, so we * can potentially drop frames. */ class OutputSurface implements SurfaceTexture.OnFrameAvailableListener { private static final String TAG = "OutputSurface"; private static final boolean VERBOSE = false; private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY; private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT; private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; private SurfaceTexture mSurfaceTexture; private Surface mSurface; private Object mFrameSyncObject = new Object(); // guards mFrameAvailable private boolean mFrameAvailable; private TextureRender mTextureRender; /** * Creates an OutputSurface backed by a pbuffer with the specifed dimensions. The new * EGL context and surface will be made current. Creates a Surface that can be passed * to MediaCodec.configure(). */ public OutputSurface(int width, int height) { if (width <= 0 || height <= 0) { throw new IllegalArgumentException(); } eglSetup(width, height); makeCurrent(); setup(); } /** * Creates an OutputSurface using the current EGL context (rather than establishing a * new one). Creates a Surface that can be passed to MediaCodec.configure(). */ public OutputSurface() { setup(); } /** * Creates instances of TextureRender and SurfaceTexture, and a Surface associated * with the SurfaceTexture. */ private void setup() { mTextureRender = new TextureRender(); mTextureRender.surfaceCreated(); // Even if we don't access the SurfaceTexture after the constructor returns, we // still need to keep a reference to it. The Surface doesn't retain a reference // at the Java level, so if we don't either then the object can get GCed, which // causes the native finalizer to run. if (VERBOSE) Log.d(TAG, "textureID=" + mTextureRender.getTextureId()); mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId()); // This doesn't work if OutputSurface is created on the thread that CTS started for // these test cases. // // The CTS-created thread has a Looper, and the SurfaceTexture constructor will // create a Handler that uses it. The "frame available" message is delivered // there, but since we're not a Looper-based thread we'll never see it. For // this to do anything useful, OutputSurface must be created on a thread without // a Looper, so that SurfaceTexture uses the main application Looper instead. // // Java language note: passing "this" out of a constructor is generally unwise, // but we should be able to get away with it here. mSurfaceTexture.setOnFrameAvailableListener(this); mSurface = new Surface(mSurfaceTexture); } /** * Prepares EGL. We want a GLES 2.0 context and a surface that supports pbuffer. */ private void eglSetup(int width, int height) { mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { throw new RuntimeException("unable to get EGL14 display"); } int[] version = new int[2]; if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { mEGLDisplay = null; throw new RuntimeException("unable to initialize EGL14"); } // Configure EGL for pbuffer and OpenGL ES 2.0. We want enough RGB bits // to be able to tell if the frame is reasonable. int[] attribList = { EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8, EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT, EGL14.EGL_NONE }; EGLConfig[] configs = new EGLConfig[1]; int[] numConfigs = new int[1]; if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length, numConfigs, 0)) { throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config"); } // Configure context for OpenGL ES 2.0. int[] attrib_list = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE }; mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT, attrib_list, 0); checkEglError("eglCreateContext"); if (mEGLContext == null) { throw new RuntimeException("null context"); } // Create a pbuffer surface. By using this for output, we can use glReadPixels // to test values in the output. int[] surfaceAttribs = { EGL14.EGL_WIDTH, width, EGL14.EGL_HEIGHT, height, EGL14.EGL_NONE }; mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs, 0); checkEglError("eglCreatePbufferSurface"); if (mEGLSurface == null) { throw new RuntimeException("surface was null"); } } /** * Discard all resources held by this class, notably the EGL context. */ public void release() { if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface); EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); EGL14.eglReleaseThread(); EGL14.eglTerminate(mEGLDisplay); } mSurface.release(); // this causes a bunch of warnings that appear harmless but might confuse someone: // W BufferQueue: [unnamed-3997-2] cancelBuffer: BufferQueue has been abandoned! //mSurfaceTexture.release(); mEGLDisplay = EGL14.EGL_NO_DISPLAY; mEGLContext = EGL14.EGL_NO_CONTEXT; mEGLSurface = EGL14.EGL_NO_SURFACE; mTextureRender = null; mSurface = null; mSurfaceTexture = null; } /** * Makes our EGL context and surface current. */ public void makeCurrent() { if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { throw new RuntimeException("eglMakeCurrent failed"); } } /** * Returns the Surface that we draw onto. */ public Surface getSurface() { return mSurface; } /** * Replaces the fragment shader. */ public void changeFragmentShader(String fragmentShader) { mTextureRender.changeFragmentShader(fragmentShader); } /** * Latches the next buffer into the texture. Must be called from the thread that created * the OutputSurface object, after the onFrameAvailable callback has signaled that new * data is available. */ public void awaitNewImage() { final int TIMEOUT_MS = 10000; synchronized (mFrameSyncObject) { while (!mFrameAvailable) { try { // Wait for onFrameAvailable() to signal us. Use a timeout to avoid // stalling the test if it doesn't arrive. mFrameSyncObject.wait(TIMEOUT_MS); if (!mFrameAvailable) { // TODO: if "spurious wakeup", continue while loop throw new RuntimeException("Surface frame wait timed out"); } } catch (InterruptedException ie) { // shouldn't happen throw new RuntimeException(ie); } } mFrameAvailable = false; } // Latch the data. mTextureRender.checkGlError("before updateTexImage"); mSurfaceTexture.updateTexImage(); } /** * Wait up to given timeout until new image become available. * @param timeoutMs * @return true if new image is available. false for no new image until timeout. */ public boolean checkForNewImage(int timeoutMs) { synchronized (mFrameSyncObject) { while (!mFrameAvailable) { try { // Wait for onFrameAvailable() to signal us. Use a timeout to avoid // stalling the test if it doesn't arrive. mFrameSyncObject.wait(timeoutMs); if (!mFrameAvailable) { return false; } } catch (InterruptedException ie) { // shouldn't happen throw new RuntimeException(ie); } } mFrameAvailable = false; } // Latch the data. mTextureRender.checkGlError("before updateTexImage"); mSurfaceTexture.updateTexImage(); return true; } /** * Draws the data from SurfaceTexture onto the current EGL surface. */ public void drawImage() { mTextureRender.drawFrame(mSurfaceTexture); } @Override public void onFrameAvailable(SurfaceTexture st) { if (VERBOSE) Log.d(TAG, "new frame available"); synchronized (mFrameSyncObject) { if (mFrameAvailable) { throw new RuntimeException("mFrameAvailable already set, frame could be dropped"); } mFrameAvailable = true; mFrameSyncObject.notifyAll(); } } /** * Checks for EGL errors. */ private void checkEglError(String msg) { int error; if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error)); } } }