From 8130f54fa3d7cdde59f4a88af081c44ddfb2c7f0 Mon Sep 17 00:00:00 2001 From: Sven Gothel Date: Sun, 25 Aug 2013 05:12:51 +0200 Subject: AndroidGLMediaPlayerAPI14: Fix implementation to coop w/ threaded decoder / Add EOS detection, setAudioVolume(..) GLMediaPlayerImpl.initStreamGL(..): Only require a minimum texture count of 2, which is the bare minimum to allow our algorithm to work, i.e. having a 'lastFrame' and avail/playing ringbuffer have each one frame. Android's MediaPlayer API can only deal w/ one SurfaceTexture, hence we have to fake a second SurfaceTextureFrame w/ same content to allow our implementation to work w/ the threaded decoder (min 2 frames). --- .../android/av/AndroidGLMediaPlayerAPI14.java | 171 ++++++++++++++------- .../jogamp/opengl/util/av/GLMediaPlayerImpl.java | 13 +- .../opengl/test/android/MovieCubeActivity0.java | 5 +- .../opengl/test/android/MovieSimpleActivity0.java | 5 +- .../opengl/test/android/MovieSimpleActivity1.java | 10 +- .../android/MovieSimpleActivityLauncher00a.java | 2 +- .../android/MovieSimpleActivityLauncher00b.java | 2 +- 7 files changed, 133 insertions(+), 75 deletions(-) diff --git a/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java b/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java index 3ff156ae3..39489cff4 100644 --- a/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java +++ b/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java @@ -46,6 +46,7 @@ import jogamp.opengl.util.av.GLMediaPlayerImpl; import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture.OnFrameAvailableListener; import android.media.MediaPlayer; +import android.media.MediaPlayer.OnCompletionListener; import android.net.Uri; import android.view.Surface; @@ -61,6 +62,12 @@ import android.view.Surface; *
  • Android API Level 14: {@link MediaPlayer#setSurface(Surface)}
  • *
  • Android API Level 14: {@link Surface#Surface(android.graphics.SurfaceTexture)}
  • * + *

    + * Since the MediaPlayer API can only deal w/ one SurfaceTexture, + * we enforce textureCount = 2 via {@link #validateTextureCount(int)} + * and duplicate the single texture via {@link #createTexFrames(GL, int)} .. etc. + * Two instanceds of TextureFrame are required due our framework implementation w/ Ringbuffer and 'lastFrame' access. + *

    */ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl { static final boolean available; @@ -77,9 +84,13 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl { public static final boolean isAvailable() { return available; } - MediaPlayer mp; - volatile boolean updateSurface = false; - Object updateSurfaceLock = new Object(); + private MediaPlayer mp; + private volatile boolean updateSurface = false; + private Object updateSurfaceLock = new Object(); + private SurfaceTextureFrame singleSTexFrame = null; + private int sTexFrameCount = 0; + private boolean sTexFrameAttached = false; + private volatile boolean eos = false; /** private static String toString(MediaPlayer m) { @@ -122,6 +133,8 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl { if(null != mp) { try { mp.start(); + eos = false; + mp.setOnCompletionListener(onCompletionListener); return true; } catch (IllegalStateException ise) { if(DEBUG) { @@ -185,22 +198,16 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl { } } - SurfaceTexture stex = null; public static class SurfaceTextureFrame extends TextureSequence.TextureFrame { public SurfaceTextureFrame(Texture t, SurfaceTexture stex) { super(t); this.surfaceTex = stex; - this.surface = new Surface(stex); } - public final SurfaceTexture getSurfaceTexture() { return surfaceTex; } - public final Surface getSurface() { return surface; } - public String toString() { return "SurfaceTextureFrame[pts " + pts + " ms, l " + duration + " ms, texID "+ texture.getTextureObject() + ", " + surfaceTex + "]"; } - private final SurfaceTexture surfaceTex; - private final Surface surface; + public final SurfaceTexture surfaceTex; } @Override @@ -221,18 +228,15 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl { } catch (IllegalStateException e) { throw new RuntimeException(e); } - if( null == stex ) { - throw new InternalError("XXX"); - } mp.setSurface(null); try { mp.prepare(); } catch (IOException ioe) { throw new IOException("MediaPlayer failed to process stream <"+streamLoc.toString()+">: "+ioe.getMessage(), ioe); } - final int r_aid = GLMediaPlayer.STREAM_ID_NONE == aid ? GLMediaPlayer.STREAM_ID_NONE : GLMediaPlayer.STREAM_ID_AUTO; + final int r_aid = GLMediaPlayer.STREAM_ID_NONE == aid ? GLMediaPlayer.STREAM_ID_NONE : 1 /* fake */; final String icodec = "android"; - updateAttributes(GLMediaPlayer.STREAM_ID_AUTO, r_aid, + updateAttributes(0 /* fake */, r_aid, mp.getVideoWidth(), mp.getVideoHeight(), 0, 0, 0, 0f, 0, 0, mp.getDuration(), icodec, icodec); @@ -243,66 +247,129 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl { // NOP } + /** + * {@inheritDoc} + *

    + * Returns 2 - implementation duplicates single texture + *

    + */ + @Override + protected int validateTextureCount(int desiredTextureCount) { + return 2; + } + @Override protected final int getNextTextureImpl(GL gl, TextureFrame nextFrame) { int pts = TimeFrameI.INVALID_PTS; - if(null != stex && null != mp) { - final SurfaceTextureFrame nextSFrame = (SurfaceTextureFrame) nextFrame; - final Surface nextSurface = nextSFrame.getSurface(); - mp.setSurface(nextSurface); - nextSurface.release(); - - // Only block once, no while-loop. - // This relaxes locking code of non crucial resources/events. - boolean update = updateSurface; - if( !update ) { - synchronized(updateSurfaceLock) { - if(!updateSurface) { // volatile OK. - try { - updateSurfaceLock.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); + if(null != mp) { + final SurfaceTextureFrame sTexFrame = (SurfaceTextureFrame) nextFrame; + final SurfaceTexture surfTex = sTexFrame.surfaceTex; + if( sTexFrame != singleSTexFrame ) { + throw new InternalError("XXX: sTexFrame: "+sTexFrame+", singleSTexFrame "+singleSTexFrame); + } + if( !sTexFrameAttached ) { + sTexFrameAttached = true; + final Surface surface = new Surface(sTexFrame.surfaceTex); + mp.setSurface(surface); + surface.release(); + surfTex.setOnFrameAvailableListener(onFrameAvailableListener); + } + if( eos || !mp.isPlaying() ) { + eos = true; + pts = TimeFrameI.END_OF_STREAM_PTS; + } else { + // Only block once, no while-loop. + // This relaxes locking code of non crucial resources/events. + boolean update = updateSurface; + if( !update ) { + synchronized(updateSurfaceLock) { + if(!updateSurface) { // volatile OK. + try { + updateSurfaceLock.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } } + update = updateSurface; + updateSurface = false; } - update = updateSurface; - updateSurface = false; + } + if(update) { + surfTex.updateTexImage(); + // nextFrame.setPTS( (int) ( nextSTex.getTimestamp() / 1000000L ) ); // nano -9 -> milli -3 + pts = mp.getCurrentPosition(); + // stex.getTransformMatrix(atex.getSTMatrix()); } } - if(update) { - final SurfaceTexture nextSTex = nextSFrame.getSurfaceTexture(); - nextSTex.updateTexImage(); - // nextFrame.setPTS( (int) ( nextSTex.getTimestamp() / 1000000L ) ); // nano -9 -> milli -3 - pts = mp.getCurrentPosition(); - nextFrame.setPTS( pts ); - // stex.getTransformMatrix(atex.getSTMatrix()); - } + nextFrame.setPTS( pts ); } return pts; } + /** + * {@inheritDoc} + *

    + * Creates only one single texture and duplicated content to 2 TextureFrames + *

    + */ + @Override + protected TextureFrame[] createTexFrames(GL gl, final int count) { + final int[] texNames = new int[1]; + gl.glGenTextures(1, texNames, 0); + final int err = gl.glGetError(); + if( GL.GL_NO_ERROR != err ) { + throw new RuntimeException("TextureNames creation failed (num: 1/"+count+"): err "+toHexString(err)); + } + final TextureFrame[] texFrames = new TextureFrame[count]; + for(int i=0; i + * Returns the single texture, which is created at 1st call. + *

    + */ @Override protected final TextureSequence.TextureFrame createTexImage(GL gl, int texName) { - if( null != stex ) { - throw new InternalError("XXX"); + sTexFrameCount++; + if( 1 == sTexFrameCount ) { + singleSTexFrame = new SurfaceTextureFrame( createTexImageImpl(gl, texName, width, height, true), new SurfaceTexture(texName) ); } - stex = new SurfaceTexture(texName); // only 1 texture - stex.setOnFrameAvailableListener(onFrameAvailableListener); - return new TextureSequence.TextureFrame( createTexImageImpl(gl, texName, width, height, true) ); + return singleSTexFrame; } + /** + * {@inheritDoc} + *

    + * Destroys the single texture at last call. + *

    + */ @Override - protected final void destroyTexFrame(GL gl, TextureSequence.TextureFrame imgTex) { - if(null != stex) { - stex.release(); - stex = null; + protected final void destroyTexFrame(GL gl, TextureSequence.TextureFrame frame) { + sTexFrameCount--; + if( 0 == sTexFrameCount ) { + singleSTexFrame = null; + sTexFrameAttached = false; + final SurfaceTextureFrame sFrame = (SurfaceTextureFrame) frame; + sFrame.surfaceTex.release(); + super.destroyTexFrame(gl, frame); } - super.destroyTexFrame(gl, imgTex); } - protected OnFrameAvailableListener onFrameAvailableListener = new OnFrameAvailableListener() { + private OnFrameAvailableListener onFrameAvailableListener = new OnFrameAvailableListener() { @Override public void onFrameAvailable(SurfaceTexture surfaceTexture) { wakeUp(true); } }; + + private OnCompletionListener onCompletionListener = new OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + eos = true; + } + }; } diff --git a/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java b/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java index 05cc997ca..73d5e7748 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java +++ b/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java @@ -453,8 +453,8 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { } if( STREAM_ID_NONE != vid ) { textureCount = validateTextureCount(reqTextureCount); - if( textureCount < TEXTURE_COUNT_MIN ) { - throw new InternalError("Validated texture count < "+TEXTURE_COUNT_MIN+": "+textureCount); + if( textureCount < 2 ) { + throw new InternalError("Validated texture count < 2: "+textureCount); } } else { textureCount = 0; @@ -545,14 +545,17 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { /** * Returns the validated number of textures to be handled. *

    - * Default is {@link #TEXTURE_COUNT_MIN} textures, last texture and the decoding texture. + * Default is {@link #TEXTURE_COUNT_MIN} minimum textures. + *

    + *

    + * Implementation must at least return a texture count of two, the last texture and the decoding texture. *

    */ protected int validateTextureCount(int desiredTextureCount) { return desiredTextureCount < TEXTURE_COUNT_MIN ? TEXTURE_COUNT_MIN : desiredTextureCount; } - private final TextureFrame[] createTexFrames(GL gl, final int count) { + protected TextureFrame[] createTexFrames(GL gl, final int count) { final int[] texNames = new int[count]; gl.glGenTextures(count, texNames, 0); final int err = gl.glGetError(); @@ -783,7 +786,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { * Audio frames shall be ignored, if {@link #getAID()} is {@link #STREAM_ID_NONE}. *

    *

    - * Methods is invoked on the StreamWorker decoding thread. + * Method may be invoked on the StreamWorker decoding thread. *

    *

    * Implementation shall care of OpenGL synchronization as required, e.g. glFinish()/glFlush()! diff --git a/src/test/com/jogamp/opengl/test/android/MovieCubeActivity0.java b/src/test/com/jogamp/opengl/test/android/MovieCubeActivity0.java index 783742fec..3d83bfd2c 100644 --- a/src/test/com/jogamp/opengl/test/android/MovieCubeActivity0.java +++ b/src/test/com/jogamp/opengl/test/android/MovieCubeActivity0.java @@ -119,10 +119,7 @@ public class MovieCubeActivity0 extends NewtBaseActivity { if( null != se ) { se.printStackTrace(); } - new Thread() { - public void run() { - glWindowMain.destroy(); - } }.start(); + getActivity().finish(); } } }); diff --git a/src/test/com/jogamp/opengl/test/android/MovieSimpleActivity0.java b/src/test/com/jogamp/opengl/test/android/MovieSimpleActivity0.java index 467ad1e75..db73673a7 100644 --- a/src/test/com/jogamp/opengl/test/android/MovieSimpleActivity0.java +++ b/src/test/com/jogamp/opengl/test/android/MovieSimpleActivity0.java @@ -116,10 +116,7 @@ public class MovieSimpleActivity0 extends NewtBaseActivity { if( null != se ) { se.printStackTrace(); } - new Thread() { - public void run() { - glWindowMain.destroy(); - } }.start(); + getActivity().finish(); } } }); diff --git a/src/test/com/jogamp/opengl/test/android/MovieSimpleActivity1.java b/src/test/com/jogamp/opengl/test/android/MovieSimpleActivity1.java index 84e691e76..f94390dcd 100644 --- a/src/test/com/jogamp/opengl/test/android/MovieSimpleActivity1.java +++ b/src/test/com/jogamp/opengl/test/android/MovieSimpleActivity1.java @@ -153,10 +153,7 @@ public class MovieSimpleActivity1 extends NewtBaseActivity { if( null != se ) { se.printStackTrace(); } - new Thread() { - public void run() { - glWindowMain.destroy(); - } }.start(); + getActivity().finish(); } } }); @@ -203,10 +200,7 @@ public class MovieSimpleActivity1 extends NewtBaseActivity { if( null != se ) { se.printStackTrace(); } - new Thread() { - public void run() { - glWindowHUD.destroy(); - } }.start(); + getActivity().finish(); } } }); diff --git a/src/test/com/jogamp/opengl/test/android/MovieSimpleActivityLauncher00a.java b/src/test/com/jogamp/opengl/test/android/MovieSimpleActivityLauncher00a.java index de4238fad..e70e48ca3 100644 --- a/src/test/com/jogamp/opengl/test/android/MovieSimpleActivityLauncher00a.java +++ b/src/test/com/jogamp/opengl/test/android/MovieSimpleActivityLauncher00a.java @@ -45,7 +45,7 @@ public class MovieSimpleActivityLauncher00a extends LauncherUtil.BaseActivityLau props.setProperty("jnlp.mplayer.hud", "false"); props.setProperty("jnlp.mplayer.hud.shared", "false"); // props.setProperty("jnlp.media0_url2", "http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_640x360.m4v"); - props.setProperty("jnlp.media0_url2", ""); + props.setProperty("jnlp.media0_url2", "http://video.webmfiles.org/big-buck-bunny_trailer.webm"); props.setProperty("jnlp.media0_url1", "http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4"); props.setProperty("jnlp.media0_url0", "file:///mnt/sdcard/Movies/BigBuckBunny_320x180.mp4"); props.setProperty("jnlp.media1_url0", "http://archive.org/download/ElephantsDream/ed_1024_512kb.mp4"); diff --git a/src/test/com/jogamp/opengl/test/android/MovieSimpleActivityLauncher00b.java b/src/test/com/jogamp/opengl/test/android/MovieSimpleActivityLauncher00b.java index 3decf393f..b0e7fdb33 100644 --- a/src/test/com/jogamp/opengl/test/android/MovieSimpleActivityLauncher00b.java +++ b/src/test/com/jogamp/opengl/test/android/MovieSimpleActivityLauncher00b.java @@ -45,7 +45,7 @@ public class MovieSimpleActivityLauncher00b extends LauncherUtil.BaseActivityLau props.setProperty("jnlp.mplayer.hud", "false"); props.setProperty("jnlp.mplayer.hud.shared", "false"); // props.setProperty("jnlp.media0_url2", "http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_640x360.m4v"); - props.setProperty("jnlp.media0_url2", ""); + props.setProperty("jnlp.media0_url2", "http://video.webmfiles.org/big-buck-bunny_trailer.webm"); props.setProperty("jnlp.media0_url1", "http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4"); props.setProperty("jnlp.media0_url0", "file:///mnt/sdcard/Movies/BigBuckBunny_320x180.mp4"); props.setProperty("jnlp.media1_url0", "http://archive.org/download/ElephantsDream/ed_1024_512kb.mp4"); -- cgit v1.2.3