Back to project page AndroidVideoSamples.
The source code is released under:
Apache License
If you think the Android project AndroidVideoSamples listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
/* * Copyright (C) 2013 The Android Open Source Project */* ww w. j a v a 2 s .c om*/ * 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. */ package com.roryhool.commonvideolibrary; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; import android.graphics.SurfaceTexture; import android.opengl.EGL14; 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 static final int EGL_OPENGL_ES2_BIT = 4; private EGL10 mEGL; private EGLDisplay mEGLDisplay; private EGLContext mEGLContext; private EGLSurface mEGLSurface; 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. 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 ) { mEGL = (EGL10) EGLContext.getEGL(); mEGLDisplay = mEGL.eglGetDisplay( EGL10.EGL_DEFAULT_DISPLAY ); if ( !mEGL.eglInitialize( mEGLDisplay, null ) ) { throw new RuntimeException( "unable to initialize EGL10" ); } // 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 = { EGL10.EGL_RED_SIZE, 8, EGL10.EGL_GREEN_SIZE, 8, EGL10.EGL_BLUE_SIZE, 8, EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT, EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL10.EGL_NONE }; EGLConfig[] configs = new EGLConfig[1]; int[] numConfigs = new int[1]; if ( !mEGL.eglChooseConfig( mEGLDisplay, attribList, configs, 1, numConfigs ) ) { throw new RuntimeException( "unable to find RGB888+pbuffer EGL config" ); } // Configure context for OpenGL ES 2.0. int[] attrib_list = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; mEGLContext = mEGL.eglCreateContext( mEGLDisplay, configs[0], EGL10.EGL_NO_CONTEXT, attrib_list ); 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 = { EGL10.EGL_WIDTH, width, EGL10.EGL_HEIGHT, height, EGL10.EGL_NONE }; mEGLSurface = mEGL.eglCreatePbufferSurface( mEGLDisplay, configs[0], surfaceAttribs ); 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 ( mEGL != null ) { if ( mEGL.eglGetCurrentContext().equals( mEGLContext ) ) { // Clear the current context and surface to ensure they are discarded immediately. mEGL.eglMakeCurrent( mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT ); } mEGL.eglDestroySurface( mEGLDisplay, mEGLSurface ); mEGL.eglDestroyContext( mEGLDisplay, mEGLContext ); // mEGL.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(); // null everything out so future attempts to use this object will cause an NPE mEGLDisplay = null; mEGLContext = null; mEGLSurface = null; mEGL = null; mTextureRender = null; mSurface = null; mSurfaceTexture = null; } /** * Makes our EGL context and surface current. */ public void makeCurrent() { if ( mEGL == null ) { throw new RuntimeException( "not configured for makeCurrent" ); } checkEglError( "before makeCurrent" ); if ( !mEGL.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 = 500; 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(); } /** * 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 ) { boolean failed = false; int error; while ( ( error = mEGL.eglGetError() ) != EGL10.EGL_SUCCESS ) { Log.e( TAG, msg + ": EGL error: 0x" + Integer.toHexString( error ) ); failed = true; } if ( failed ) { throw new RuntimeException( "EGL error encountered (see log)" ); } } }