diff options
Diffstat (limited to 'libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/OutputSurface.java')
-rw-r--r-- | libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/OutputSurface.java | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/OutputSurface.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/OutputSurface.java new file mode 100644 index 000000000..e52ba0217 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/OutputSurface.java @@ -0,0 +1,276 @@ +/* + * 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. + * <p> + * 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. + * <p> + * 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. + * <p> + * 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)); + } + } +} |