diff options
author | Sven Gothel <[email protected]> | 2023-03-13 05:59:34 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2023-03-13 05:59:34 +0100 |
commit | 913b00f8b876e29af91677ef61b3eb35d6853e6e (patch) | |
tree | 17bc28ad255f1e086b2aa80028d4e6fb1ffd0456 /src/jogl/classes | |
parent | 5efd3a6d9cf12d38ce6d7c91f9c5968927f3253a (diff) |
GLMediaPlayer: Overhaul and simplify states, allow usage before stream ready showing test-texture. Adding stop(); (API Change)
- allow multiple initGL(..) @ uninitialized and initialized
- allows usage before stream is ready
- using a test-texture @ uninitialized
- adding stop()
API change
- initStream() -> playStream()
- play() -> resume()
FFMPEG: Added 'ready' check for robustness
Diffstat (limited to 'src/jogl/classes')
7 files changed, 219 insertions, 96 deletions
diff --git a/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java b/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java index c2de32372..5740dae4b 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java +++ b/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java @@ -37,6 +37,7 @@ import com.jogamp.common.net.Uri; import com.jogamp.opengl.util.texture.Texture; import com.jogamp.opengl.util.texture.TextureSequence; import com.jogamp.opengl.util.TimeFrameI; +import com.jogamp.opengl.util.av.GLMediaPlayer.State; /** * GLMediaPlayer interface specifies a {@link TextureSequence} state machine @@ -45,7 +46,7 @@ import com.jogamp.opengl.util.TimeFrameI; * Audio maybe supported and played back internally or via an {@link AudioSink} implementation. * </p> * <p> - * Audio and video streams can be selected or muted via {@link #initStream(Uri, int, int, int)} + * Audio and video streams can be selected or muted via {@link #playStream(Uri, int, int, int)} * using the appropriate <a href="#streamIDs">stream id</a>'s. * </p> * <p> @@ -56,7 +57,7 @@ import com.jogamp.opengl.util.TimeFrameI; * <p> * Most of the stream processing is performed on the decoding thread, a.k.a. <i>StreamWorker</i>: * <ul> - * <li>Stream initialization triggered by {@link #initStream(Uri, int, int, int) initStream(..)} - User gets notified whether the stream has been initialized or not via {@link GLMediaEventListener#attributesChanged(GLMediaPlayer, int, long) attributesChanges(..)}.</li> + * <li>Stream initialization triggered by {@link #playStream(Uri, int, int, int) playStream(..)} - User gets notified whether the stream has been initialized or not via {@link GLMediaEventListener#attributesChanged(GLMediaPlayer, int, long) attributesChanges(..)}.</li> * <li>Stream decoding - User gets notified of a new frame via {@link GLMediaEventListener#newFrameAvailable(GLMediaPlayer, com.jogamp.opengl.util.texture.TextureSequence.TextureFrame, long) newFrameAvailable(...)}.</li> * <li>Caught <a href="#streamerror">exceptions on the decoding thread</a> are delivered as {@link StreamException}s.</li> * </ul> @@ -82,16 +83,17 @@ import com.jogamp.opengl.util.TimeFrameI; * <p> * <table border="1"> * <tr><th>Action</th> <th>{@link State} Before</th> <th>{@link State} After</th> <th>{@link GLMediaEventListener Event}</th></tr> - * <tr><td>{@link #initStream(Uri, int, int, int)}</td> <td>{@link State#Uninitialized Uninitialized}</td> <td>{@link State#Initialized Initialized}<sup><a href="#streamworker">1</a></sup>, {@link State#Uninitialized Uninitialized}</td> <td>{@link GLMediaEventListener#EVENT_CHANGE_INIT EVENT_CHANGE_INIT} or ( {@link GLMediaEventListener#EVENT_CHANGE_ERR EVENT_CHANGE_ERR} + {@link GLMediaEventListener#EVENT_CHANGE_UNINIT EVENT_CHANGE_UNINIT} )</td></tr> - * <tr><td>{@link #initGL(GL)}</td> <td>{@link State#Initialized Initialized}</td> <td>{@link State#Paused Paused}, , {@link State#Uninitialized Uninitialized}</td> <td>{@link GLMediaEventListener#EVENT_CHANGE_PAUSE EVENT_CHANGE_PAUSE} or ( {@link GLMediaEventListener#EVENT_CHANGE_ERR EVENT_CHANGE_ERR} + {@link GLMediaEventListener#EVENT_CHANGE_UNINIT EVENT_CHANGE_UNINIT} )</td></tr> - * <tr><td>{@link #play()}</td> <td>{@link State#Paused Paused}</td> <td>{@link State#Playing Playing}</td> <td>{@link GLMediaEventListener#EVENT_CHANGE_PLAY EVENT_CHANGE_PLAY}</td></tr> + * <tr><td>{@link #playStream(Uri, int, int, int)}</td> <td>{@link State#Uninitialized Uninitialized}</td> <td>{@link State#Initialized Initialized}<sup><a href="#streamworker">1</a></sup>, {@link State#Uninitialized Uninitialized}</td> <td>{@link GLMediaEventListener#EVENT_CHANGE_INIT EVENT_CHANGE_INIT} or ( {@link GLMediaEventListener#EVENT_CHANGE_ERR EVENT_CHANGE_ERR} + {@link GLMediaEventListener#EVENT_CHANGE_UNINIT EVENT_CHANGE_UNINIT} )</td></tr> + * <tr><td>{@link #initGL(GL)}</td> <td>{@link State#Initialized Initialized}, {@link State#Uninitialized Uninitialized} </td> <td>{@link State#Playing Playing}, {@link State#Uninitialized Uninitialized}</td> <td>{@link GLMediaEventListener#EVENT_CHANGE_PLAY EVENT_CHANGE_PLAY} or ( {@link GLMediaEventListener#EVENT_CHANGE_ERR EVENT_CHANGE_ERR} + {@link GLMediaEventListener#EVENT_CHANGE_UNINIT EVENT_CHANGE_UNINIT} )</td></tr> * <tr><td>{@link #pause(boolean)}</td> <td>{@link State#Playing Playing}</td> <td>{@link State#Paused Paused}</td> <td>{@link GLMediaEventListener#EVENT_CHANGE_PAUSE EVENT_CHANGE_PAUSE}</td></tr> + * <tr><td>{@link #resume()}</td> <td>{@link State#Paused Paused}</td> <td>{@link State#Playing Playing}</td> <td>{@link GLMediaEventListener#EVENT_CHANGE_PLAY EVENT_CHANGE_PLAY}</td></tr> + * <tr><td>{@link #stop()}</td> <td>{@link State#Playing Playing}, {@link State#Paused Paused}</td> <td>{@link State#Uninitialized Uninitialized}</td> <td>{@link GLMediaEventListener#EVENT_CHANGE_PAUSE EVENT_CHANGE_PAUSE}</td></tr> * <tr><td>{@link #seek(int)}</td> <td>{@link State#Paused Paused}, {@link State#Playing Playing}</td> <td>{@link State#Paused Paused}, {@link State#Playing Playing}</td> <td>none</td></tr> - * <tr><td>{@link #getNextTexture(GL)}</td> <td>{@link State#Paused Paused}, {@link State#Playing Playing}</td> <td>{@link State#Paused Paused}, {@link State#Playing Playing}</td> <td>none</td></tr> - * <tr><td>{@link #getLastTexture()}</td> <td>{@link State#Paused Paused}, {@link State#Playing Playing}</td> <td>{@link State#Paused Paused}, {@link State#Playing Playing}</td> <td>none</td></tr> + * <tr><td>{@link #getNextTexture(GL)}</td> <td><i>any</i></td> <td><i>same</i></td> <td>none</td></tr> + * <tr><td>{@link #getLastTexture()}</td> <td><i>any</i></td> <td><i>same</i></td> <td>none</td></tr> * <tr><td>{@link TextureFrame#END_OF_STREAM_PTS END_OF_STREAM}</td> <td>{@link State#Playing Playing}</td> <td>{@link State#Paused Paused}</td> <td>{@link GLMediaEventListener#EVENT_CHANGE_EOS EVENT_CHANGE_EOS} + {@link GLMediaEventListener#EVENT_CHANGE_PAUSE EVENT_CHANGE_PAUSE}</td></tr> - * <tr><td>{@link StreamException}</td> <td>ANY</td> <td>{@link State#Paused Paused}, {@link State#Uninitialized Uninitialized}</td> <td>{@link GLMediaEventListener#EVENT_CHANGE_ERR EVENT_CHANGE_ERR} + ( {@link GLMediaEventListener#EVENT_CHANGE_PAUSE EVENT_CHANGE_PAUSE} or {@link GLMediaEventListener#EVENT_CHANGE_UNINIT EVENT_CHANGE_UNINIT} )</td></tr> - * <tr><td>{@link #destroy(GL)}</td> <td>ANY</td> <td>{@link State#Uninitialized Uninitialized}</td> <td>{@link GLMediaEventListener#EVENT_CHANGE_UNINIT EVENT_CHANGE_UNINIT}</td></tr> + * <tr><td>{@link StreamException}</td> <td><i>any</i></td> <td>{@link State#Paused Paused}, {@link State#Uninitialized Uninitialized}</td> <td>{@link GLMediaEventListener#EVENT_CHANGE_ERR EVENT_CHANGE_ERR} + ( {@link GLMediaEventListener#EVENT_CHANGE_PAUSE EVENT_CHANGE_PAUSE} or {@link GLMediaEventListener#EVENT_CHANGE_UNINIT EVENT_CHANGE_UNINIT} )</td></tr> + * <tr><td>{@link #destroy(GL)}</td> <td><i>any</i></td> <td>{@link State#Uninitialized Uninitialized}</td> <td>{@link GLMediaEventListener#EVENT_CHANGE_UNINIT EVENT_CHANGE_UNINIT}</td></tr> * </table> * </p> * @@ -188,6 +190,7 @@ import com.jogamp.opengl.util.TimeFrameI; */ public interface GLMediaPlayer extends TextureSequence { public static final boolean DEBUG = Debug.debug("GLMediaPlayer"); + public static final boolean DEBUG_AVSYNC = Debug.debug("GLMediaPlayer.AVSync"); public static final boolean DEBUG_NATIVE = Debug.debug("GLMediaPlayer.Native"); /** Default texture count, value {@value}. */ @@ -372,7 +375,7 @@ public interface GLMediaPlayer extends TextureSequence { * @throws IllegalArgumentException if arguments are invalid * @since 2.3.0 */ - public void initStream(Uri streamLoc, int vid, int aid, int textureCount) throws IllegalStateException, IllegalArgumentException; + public void playStream(Uri streamLoc, int vid, int aid, int textureCount) throws IllegalStateException, IllegalArgumentException; /** * Returns the {@link StreamException} caught in the decoder thread, or <code>null</code> if none occured. @@ -389,7 +392,7 @@ public interface GLMediaPlayer extends TextureSequence { * <p> * <a href="#lifecycle">Lifecycle</a>: {@link State#Initialized} -> {@link State#Paused} or {@link State#Initialized} * </p> - * Argument <code>gl</code> is ignored if video is muted, see {@link #initStream(Uri, int, int, int)}. + * Argument <code>gl</code> is ignored if video is muted, see {@link #playStream(Uri, int, int, int)}. * * @param gl current GL object. Maybe <code>null</code>, for audio only. * @throws IllegalStateException if not invoked in {@link State#Initialized}. @@ -401,7 +404,7 @@ public interface GLMediaPlayer extends TextureSequence { /** * If implementation uses a {@link AudioSink}, it's instance will be returned. * <p> - * The {@link AudioSink} instance is available after {@link #initStream(Uri, int, int, int)}, + * The {@link AudioSink} instance is available after {@link #playStream(Uri, int, int, int)}, * if used by implementation. * </p> */ @@ -416,6 +419,14 @@ public interface GLMediaPlayer extends TextureSequence { public State destroy(GL gl); /** + * Stops streaming and releases the GL, stream and other resources, but keeps {@link #attachObject(String, Object) attached user objects}. + * <p> + * <a href="#lifecycle">Lifecycle</a>: <code>ANY</code> -> {@link State#Uninitialized} + * </p> + */ + public State stop(); + + /** * Sets the playback speed. * <p> * To simplify test, play speed is <i>normalized</i>, i.e. @@ -452,7 +463,7 @@ public interface GLMediaPlayer extends TextureSequence { * <a href="#lifecycle">Lifecycle</a>: {@link State#Paused} -> {@link State#Playing} * </p> */ - public State play(); + public State resume(); /** * Pauses the <i>StreamWorker</i> decoding thread. @@ -460,7 +471,7 @@ public interface GLMediaPlayer extends TextureSequence { * <a href="#lifecycle">Lifecycle</a>: {@link State#Playing} -> {@link State#Paused} * </p> * <p> - * If a <i>new</i> frame is desired after the next {@link #play()} call, + * If a <i>new</i> frame is desired after the next {@link #resume()} call, * e.g. to make a snapshot of a camera input stream, * <code>flush</code> shall be set to <code>true</code>. * </p> @@ -498,13 +509,13 @@ public interface GLMediaPlayer extends TextureSequence { public int getAID(); /** - * @return the current decoded frame count since {@link #play()} and {@link #seek(int)} + * @return the current decoded frame count since {@link #resume()} and {@link #seek(int)} * as increased by {@link #getNextTexture(GL)} or the decoding thread. */ public int getDecodedFrameCount(); /** - * @return the current presented frame count since {@link #play()} and {@link #seek(int)} + * @return the current presented frame count since {@link #resume()} and {@link #seek(int)} * as increased by {@link #getNextTexture(GL)} for new frames. */ public int getPresentedFrameCount(); @@ -547,7 +558,7 @@ public interface GLMediaPlayer extends TextureSequence { public TextureSequence.TextureFrame getNextTexture(GL gl) throws IllegalStateException; /** - * Return the stream location, as set by {@link #initStream(Uri, int, int, int)}. + * Return the stream location, as set by {@link #playStream(Uri, int, int, int)}. * @since 2.3.0 */ public Uri getUri(); diff --git a/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java b/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java index 09d2dfda0..06eca7128 100644 --- a/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java +++ b/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java @@ -136,7 +136,7 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl { } @Override - protected final boolean playImpl() { + protected final boolean resumeImpl() { playStart = Platform.currentTimeMillis(); if(null != mp) { try { @@ -212,7 +212,7 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl { protected final int getAudioPTSImpl() { return null != mp ? mp.getCurrentPosition() : 0; } @Override - protected final void destroyImpl(final GL gl) { + protected final void destroyImpl() { if(null != mp) { wakeUp(false); try { @@ -239,12 +239,18 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl { } } + @Override + protected void stopImpl() { + destroyImpl(); + } + public static class SurfaceTextureFrame extends TextureSequence.TextureFrame { public SurfaceTextureFrame(final Texture t, final SurfaceTexture stex) { super(t); this.surfaceTex = stex; } + @Override public String toString() { return "SurfaceTextureFrame[pts " + pts + " ms, l " + duration + " ms, texID "+ texture.getTextureObject() + ", " + surfaceTex + "]"; } diff --git a/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java b/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java index bdd9b6c95..c52c59f17 100644 --- a/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java +++ b/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java @@ -89,6 +89,7 @@ public class ALAudioSink implements AudioSink { /** Get this frame's OpenAL buffer name */ public final int getALBuffer() { return alBuffer; } + @Override public String toString() { return "ALAudioFrame[pts " + pts + " ms, l " + duration + " ms, " + byteSize + " bytes, buffer "+alBuffer+"]"; } diff --git a/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java b/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java index 01e04f6de..d29464552 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java +++ b/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java @@ -28,6 +28,9 @@ package jogamp.opengl.util.av; import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URLConnection; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -46,9 +49,11 @@ import com.jogamp.opengl.GLProfile; import jogamp.opengl.Debug; import com.jogamp.common.net.UriQueryProps; +import com.jogamp.common.nio.Buffers; import com.jogamp.common.ExceptionUtils; import com.jogamp.common.net.Uri; import com.jogamp.common.os.Platform; +import com.jogamp.common.util.IOUtil; import com.jogamp.common.util.InterruptSource; import com.jogamp.common.util.InterruptedRuntimeException; import com.jogamp.common.util.LFRingbuffer; @@ -60,6 +65,8 @@ import com.jogamp.opengl.util.av.AudioSink; import com.jogamp.opengl.util.av.GLMediaPlayer; import com.jogamp.opengl.util.glsl.ShaderCode; import com.jogamp.opengl.util.texture.Texture; +import com.jogamp.opengl.util.texture.TextureData; +import com.jogamp.opengl.util.texture.TextureIO; import com.jogamp.opengl.util.texture.TextureSequence; import com.jogamp.opengl.util.texture.TextureSequence.TextureFrame; @@ -96,12 +103,12 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { private final int[] texWrapST = { GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE }; /** User requested URI stream location. */ - private Uri streamLoc = null; + private Uri streamLoc; /** * In case {@link #streamLoc} is a {@link GLMediaPlayer#CameraInputScheme}, * {@link #cameraPath} holds the URI's path portion - * as parsed in {@link #initStream(Uri, int, int, int)}. + * as parsed in {@link #playStream(Uri, int, int, int)}. * @see #cameraProps */ protected Uri.Encoded cameraPath = null; @@ -112,9 +119,9 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { private float audioVolume = 1.0f; /** Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */ - private int vid = GLMediaPlayer.STREAM_ID_AUTO; + private int vid = GLMediaPlayer.STREAM_ID_NONE; /** Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */ - private int aid = GLMediaPlayer.STREAM_ID_AUTO; + private int aid = GLMediaPlayer.STREAM_ID_NONE; /** Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */ private int width = 0; /** Shall be set by the {@link #initStreamImpl(int, int)} method implementation. */ @@ -202,6 +209,9 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { this.texUnit = 0; this.textureFragmentShaderHashCode = 0; this.state = State.Uninitialized; + try { + streamLoc = Uri.cast("https://no/stream/"); + } catch (final URISyntaxException e) { } } @Override @@ -237,15 +247,8 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { @Override public final int[] getTextureWrapST() { return texWrapST; } - private final void checkGLInit() { - if(State.Uninitialized == state || State.Initialized == state ) { - throw new IllegalStateException("GL not initialized: "+this); - } - } - @Override - public String getRequiredExtensionsShaderStub() throws IllegalStateException { - checkGLInit(); + public String getRequiredExtensionsShaderStub() { if(GLES2.GL_TEXTURE_EXTERNAL_OES == textureTarget) { return ShaderCode.createExtensionDirective(GLExtensions.OES_EGL_image_external, ShaderCode.ENABLE); } @@ -253,8 +256,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { } @Override - public String getTextureSampler2DType() throws IllegalStateException { - checkGLInit(); + public String getTextureSampler2DType() { switch(textureTarget) { case GL.GL_TEXTURE_2D: case GL2GL3.GL_TEXTURE_RECTANGLE: @@ -273,8 +275,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { * if not overridden by specialization. */ @Override - public String getTextureLookupFunctionName(final String desiredFuncName) throws IllegalStateException { - checkGLInit(); + public String getTextureLookupFunctionName(final String desiredFuncName) { return "texture2D"; } @@ -286,8 +287,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { * if not overridden by specialization. */ @Override - public String getTextureLookupFragmentShaderImpl() throws IllegalStateException { - checkGLInit(); + public String getTextureLookupFragmentShaderImpl() { return ""; } @@ -335,12 +335,12 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { protected final void setState(final State s) { state=s; } @Override - public final State play() { + public final State resume() { synchronized( stateLock ) { final State preState = state; switch( state ) { case Paused: - if( playImpl() ) { + if( resumeImpl() ) { resetAVPTS(); if( null != audioSink ) { audioSink.play(); // cont. w/ new data @@ -356,7 +356,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { return state; } } - protected abstract boolean playImpl(); + protected abstract boolean resumeImpl(); @Override public final State pause(final boolean flush) { @@ -378,7 +378,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { } attributesUpdated( event_mask ); if( !pauseImpl() ) { - play(); + resume(); } } if(DEBUG) { System.err.println("Pause: "+preState+" -> "+state+", "+toString()); } @@ -388,6 +388,24 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { protected abstract boolean pauseImpl(); @Override + public final State stop() { + synchronized( stateLock ) { + final State preState = state; + if( null != streamWorker ) { + streamWorker.doStop(); + streamWorker = null; + } + resetAVPTSAndFlush(); + stopImpl(); + changeState(0, State.Uninitialized); + // attachedObjects.clear(); + if(DEBUG) { System.err.println("Stop: "+preState+" -> "+state+", "+toString()); } + return state; + } + } + protected abstract void stopImpl(); + + @Override public final State destroy(final GL gl) { return destroyImpl(gl, 0, true); } @@ -397,15 +415,17 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { streamWorker.doStopImpl(wait); streamWorker = null; } - destroyImpl(gl); + resetAVPTSAndFlush(); + destroyImpl(); removeAllTextureFrames(gl); + lastFrame = null; textureCount=0; changeState(event_mask, State.Uninitialized); attachedObjects.clear(); return state; } } - protected abstract void destroyImpl(GL gl); + protected abstract void destroyImpl(); @Override public final int seek(int msec) { @@ -534,13 +554,14 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { } @Override - public final void initStream(final Uri streamLoc, final int vid, final int aid, final int reqTextureCount) throws IllegalStateException, IllegalArgumentException { + public final void playStream(final Uri streamLoc, final int vid, final int aid, final int reqTextureCount) throws IllegalStateException, IllegalArgumentException { synchronized( stateLock ) { if(State.Uninitialized != state) { throw new IllegalStateException("Instance not in state unintialized: "+this); } if(null == streamLoc) { - throw new IllegalArgumentException("streamLock is null"); + initTestStream(); + return; } if( STREAM_ID_NONE != vid ) { textureCount = validateTextureCount(reqTextureCount); @@ -617,9 +638,9 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { @Override public final void initGL(final GL gl) throws IllegalStateException, StreamException, GLException { synchronized( stateLock ) { - if(State.Initialized != state ) { - throw new IllegalStateException("Stream not in state initialized: "+this); - } + // if(State.Initialized != state && State.Uninitialized != state) { + // throw new IllegalStateException("Stream not in state [un]initialized: "+this); + // } if( null != streamWorker ) { final StreamException streamInitErr = getStreamException(); if( null != streamInitErr ) { @@ -629,7 +650,8 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { } } try { - if( STREAM_ID_NONE != vid ) { + if( STREAM_ID_NONE != vid && State.Uninitialized != state ) { + resetAVPTSAndFlush(); removeAllTextureFrames(gl); initGLImpl(gl); if(DEBUG) { @@ -645,20 +667,40 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { videoFramesDecoded = new LFRingbuffer<TextureFrame>(TextureFrame[].class, textureCount); lastFrame = videoFramesFree.getBlocking( ); } - if( null != streamWorker ) { - streamWorker.initGL(gl); + if( null == streamWorker && + ( TEXTURE_COUNT_MIN < textureCount || STREAM_ID_NONE == vid ) ) // Enable StreamWorker for 'audio only' as well (Bug 918). + { + streamWorker = new StreamWorker(); } + streamWorker.initGL(gl); + streamWorker.doResume(); + changeState(0, State.Paused); + resume(); } else { - removeAllTextureFrames(null); - initGLImpl(null); - setTextureFormat(-1, -1); - setTextureType(-1); - videoFramesOrig = null; + resetAVPTSAndFlush(); + removeAllTextureFrames(gl); + // initGLImpl(gl); + // Using a dummy test frame + width = TestTexture.singleton.getWidth(); + height = TestTexture.singleton.getHeight(); + setTextureFormat(GL.GL_RGBA, GL.GL_RGBA); + setTextureType(GL.GL_UNSIGNED_BYTE); + textureCount = Math.max(TEXTURE_COUNT_MIN, textureCount); + videoFramesOrig = createTestTexFrames(gl, textureCount); + if( TEXTURE_COUNT_MIN == textureCount ) { + videoFramesFree = null; + videoFramesDecoded = null; + lastFrame = videoFramesOrig[0]; + } else { + videoFramesFree = new LFRingbuffer<TextureFrame>(videoFramesOrig); + videoFramesDecoded = new LFRingbuffer<TextureFrame>(TextureFrame[].class, textureCount); + lastFrame = videoFramesFree.getBlocking( ); + } videoFramesFree = null; videoFramesDecoded = null; - lastFrame = null; + lastFrame = videoFramesOrig[0]; + // changeState(0, State.Paused); } - changeState(0, State.Paused); } catch (final Throwable t) { destroyImpl(gl, GLMediaEventListener.EVENT_CHANGE_ERR, false /* wait */); // -> GLMediaPlayer.State.Uninitialized throw new GLException("Error initializing GL resources", t); @@ -703,6 +745,64 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { } return texFrames; } + protected TextureFrame[] createTestTexFrames(final GL gl, final int count) { + final int[] texNames = new int[count]; + gl.glGenTextures(count, texNames, 0); + final int err = gl.glGetError(); + if( GL.GL_NO_ERROR != err ) { + throw new RuntimeException("TextureNames creation failed (num: "+count+"): err "+toHexString(err)); + } + final TextureFrame[] texFrames = new TextureFrame[count]; + for(int i=0; i<count; i++) { + texFrames[i] = createTestTexImage(gl, texNames[i]); + } + return texFrames; + } + + private static class TestTexture { + private static final TextureData singleton; + static { + TextureData data = null; + try { + final URLConnection urlConn = IOUtil.getResource("jogamp/opengl/assets/test-ntsc01-28x16.png", NullGLMediaPlayer.class.getClassLoader()); + if(null != urlConn) { + data = TextureIO.newTextureData(GLProfile.getGL2ES2(), urlConn.getInputStream(), false, TextureIO.PNG); + } + } catch (final Exception e) { + e.printStackTrace(); + } + if(null == data) { + final int w = 28; + final int h = 16; + final ByteBuffer buffer = Buffers.newDirectByteBuffer(w*h*4); + while(buffer.hasRemaining()) { + buffer.put((byte) 0xEA); buffer.put((byte) 0xEA); buffer.put((byte) 0xEA); buffer.put((byte) 0xEA); + } + buffer.rewind(); + data = new TextureData(GLProfile.getGL2ES2(), + GL.GL_RGBA, w, h, 0, + GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false, + false, false, buffer, null); + } + singleton = data; + } + } + private final TextureSequence.TextureFrame createTestTexImage(final GL gl, final int texName) { + final Texture texture = createTexImageImpl(gl, texName, TestTexture.singleton.getWidth(), TestTexture.singleton.getHeight()); + texture.updateImage(gl, TestTexture.singleton); + return new TextureSequence.TextureFrame( texture ); + } + private void initTestStream() { + textureCount = TEXTURE_COUNT_MIN; + final float _fps = 24f; + final int _duration = 10*60*1000; // msec + final int _totalFrames = (int) ( (_duration/1000)*_fps ); + updateAttributes(GLMediaPlayer.STREAM_ID_NONE, GLMediaPlayer.STREAM_ID_NONE, + TestTexture.singleton.getWidth(), TestTexture.singleton.getHeight(), 0, + 0, 0, _fps, + _totalFrames, 0, _duration, "png-static", null); + } + protected abstract TextureFrame createTexImage(GL gl, int texName); protected final Texture createTexImageImpl(final GL gl, final int texName, final int tWidth, final int tHeight) { @@ -763,9 +863,6 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { @Override public final TextureFrame getLastTexture() throws IllegalStateException { - if( State.Paused != state && State.Playing != state ) { - throw new IllegalStateException("Instance not paused or playing: "+this); - } return lastFrame; } @@ -774,7 +871,6 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { videoFramesOrig = null; videoFramesFree = null; videoFramesDecoded = null; - lastFrame = null; if( null != texFrames ) { for(int i=0; i<texFrames.length; i++) { final TextureFrame frame = texFrames[i]; @@ -799,9 +895,6 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { @Override public final TextureFrame getNextTexture(final GL gl) throws IllegalStateException { synchronized( stateLock ) { - if( State.Paused != state && State.Playing != state ) { - throw new IllegalStateException("Instance not paused or playing: "+this); - } if(State.Playing == state) { boolean dropFrame = false; try { @@ -857,7 +950,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { if( null == videoFramesDecoded || !videoFramesDecoded.isEmpty() ) { nullFrameCount++; } - if( DEBUG ) { + if( DEBUG_AVSYNC ) { final int audio_pts = getAudioPTSImpl(); final int audio_scr = (int) ( ( currentTimeMillis - audio_scr_t0 ) * playSpeed ); final int d_apts; @@ -897,7 +990,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { // final int d_avpts = d_vpts - d_apts; if( -VIDEO_DPTS_MAX > d_vpts || d_vpts > VIDEO_DPTS_MAX ) { // if( -VIDEO_DPTS_MAX > d_avpts || d_avpts > VIDEO_DPTS_MAX ) { - if( DEBUG ) { + if( DEBUG_AVSYNC ) { System.err.println( "AV*: dT "+(currentTimeMillis-lastTimeMillis)+", "+ getPerfStringImpl( video_scr, video_pts, d_vpts, audio_scr, audio_pts, d_apts, 0 ) + ", "+nextFrame+", playCached " + playCached+ ", dropFrame "+dropFrame); } @@ -922,7 +1015,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { dropFrame = true; } video_pts_last = video_pts; - if( DEBUG ) { + if( DEBUG_AVSYNC ) { System.err.println( "AV_: dT "+(currentTimeMillis-lastTimeMillis)+", "+ getPerfStringImpl( video_scr, video_pts, d_vpts, audio_scr, audio_pts, d_apts, @@ -1002,7 +1095,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { * {@inheritDoc} * <p> * Note: All {@link AudioSink} operations are performed from {@link GLMediaPlayerImpl}, - * i.e. {@link #play()}, {@link #pause(boolean)}, {@link #seek(int)}, {@link #setPlaySpeed(float)}, {@link #getAudioPTS()}. + * i.e. {@link #resume()}, {@link #pause(boolean)}, {@link #seek(int)}, {@link #setPlaySpeed(float)}, {@link #getAudioPTS()}. * </p> * <p> * Implementations using an {@link AudioSink} shall write it's instance to {@link #audioSink} @@ -1251,7 +1344,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { preNextTextureImpl(sharedGLCtx.getGL()); sharedGLCtxCurrent = true; } - if( null == videoFramesFree ) { + if( null == videoFramesFree && STREAM_ID_NONE != vid ) { throw new InternalError("XXX videoFramesFree is null"); } } @@ -1393,7 +1486,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { /** * Called initially by {@link #initStreamImpl(int, int)}, which - * is called off-thread by {@link #initStream(Uri, int, int, int)}. + * is called off-thread by {@link #playStream(Uri, int, int, int)}. * <p> * The latter catches an occurring exception and set the state delivers the error events. * </p> @@ -1470,11 +1563,10 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { return; } if( wasUninitialized ) { - if( null != streamWorker ) { - throw new InternalError("XXX: StreamWorker not null - "+this); - } - if( TEXTURE_COUNT_MIN < textureCount || STREAM_ID_NONE == vid ) { // Enable StreamWorker for 'audio only' as well (Bug 918). - streamWorker = new StreamWorker(); + if( null == streamWorker ) { + if( TEXTURE_COUNT_MIN < textureCount || STREAM_ID_NONE == vid ) { // Enable StreamWorker for 'audio only' as well (Bug 918). + streamWorker = new StreamWorker(); + } } if( DEBUG ) { System.err.println("XXX Initialize @ updateAttributes: "+this); diff --git a/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java index 59dba68f2..528b62db6 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java +++ b/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java @@ -66,7 +66,7 @@ public class NullGLMediaPlayer extends GLMediaPlayerImpl { } @Override - protected final boolean playImpl() { + protected final boolean resumeImpl() { pos_start = Platform.currentTimeMillis(); return true; } @@ -77,6 +77,10 @@ public class NullGLMediaPlayer extends GLMediaPlayerImpl { } @Override + protected final void stopImpl() { + } + + @Override protected final int seekImpl(final int msec) { pos_ms = msec; validatePos(); @@ -98,7 +102,7 @@ public class NullGLMediaPlayer extends GLMediaPlayerImpl { } @Override - protected final void destroyImpl(final GL gl) { + protected final void destroyImpl() { if(null != texData) { texData.destroy(); texData = null; diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java index aae1d75d0..91bef005a 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java +++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java @@ -297,16 +297,19 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { if(!available) { throw new RuntimeException("FFMPEGMediaPlayer not available"); } + psm = new GLPixelStorageModes(); + initSelf(); + } + private void initSelf() { moviePtr = natives.createInstance0(this, DEBUG_NATIVE); if(0==moviePtr) { throw new GLException("Couldn't create FFMPEGInstance"); } - psm = new GLPixelStorageModes(); audioSink = null; } @Override - protected final void destroyImpl(final GL gl) { + protected final void destroyImpl() { if (moviePtr != 0) { natives.destroyInstance0(moviePtr); moviePtr = 0; @@ -321,6 +324,12 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { } } + @Override + protected void stopImpl() { + destroyImpl(); + initSelf(); + } + public static final String dev_video_linux = "/dev/video"; @Override @@ -394,9 +403,6 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { if(0==moviePtr) { throw new GLException("FFMPEG native instance null"); } - if(null == audioSink) { - throw new GLException("AudioSink null"); - } final int audioQueueLimit; if( null != gl && STREAM_ID_NONE != getVID() ) { final GLContextImpl ctx = (GLContextImpl)gl.getContext(); @@ -419,8 +425,10 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { System.err.println("initGL: p3 avChosen "+avChosenAudioFormat); } - if( STREAM_ID_NONE == getAID() ) { - audioSink.destroy(); + if( STREAM_ID_NONE == getAID() || null == audioSink ) { + if(null != audioSink) { + audioSink.destroy(); + } audioSink = AudioSinkFactory.createNull(); audioSink.init(AudioSink.DefaultFormat, 0, AudioSink.DefaultInitialQueueSize, AudioSink.DefaultQueueGrowAmount, audioQueueLimit); } else { @@ -475,6 +483,7 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { } setTextureFormat(tif, tf); setTextureType(tt); + setIsGLOriented(false); if(DEBUG) { System.err.println("initGL: p5: video "+vPixelFmt+", planes "+vPlanes+", bpp "+vBitsPerPixel+"/"+vBytesPerPixelPerPlane+ ", tex "+texWidth+"x"+texHeight+", usesTexLookupShader "+usesTexLookupShader); @@ -694,10 +703,7 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { * Otherwise the call is delegated to it's super class. */ @Override - public final String getTextureLookupFunctionName(final String desiredFuncName) throws IllegalStateException { - if( State.Uninitialized == getState() ) { - throw new IllegalStateException("Instance not initialized: "+this); - } + public final String getTextureLookupFunctionName(final String desiredFuncName) { if( usesTexLookupShader ) { if(null != desiredFuncName && desiredFuncName.length()>0) { texLookupFuncName = desiredFuncName; @@ -714,10 +720,7 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { * e.g. YUV420P to RGB. Otherwise the call is delegated to it's super class. */ @Override - public final String getTextureLookupFragmentShaderImpl() throws IllegalStateException { - if( State.Uninitialized == getState() ) { - throw new IllegalStateException("Instance not initialized: "+this); - } + public final String getTextureLookupFragmentShaderImpl() { if( !usesTexLookupShader ) { return super.getTextureLookupFragmentShaderImpl(); } @@ -825,7 +828,7 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { } @Override - public final boolean playImpl() { + public final boolean resumeImpl() { if(0==moviePtr) { return false; } diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/OMXGLMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/impl/OMXGLMediaPlayer.java index c69108b04..336084734 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/impl/OMXGLMediaPlayer.java +++ b/src/jogl/classes/jogamp/opengl/util/av/impl/OMXGLMediaPlayer.java @@ -86,7 +86,7 @@ public class OMXGLMediaPlayer extends EGLMediaPlayerImpl { } @Override - protected void destroyImpl(final GL gl) { + protected void destroyImpl() { if (moviePtr != 0) { _stop(moviePtr); _detachVideoRenderer(moviePtr); @@ -137,7 +137,7 @@ public class OMXGLMediaPlayer extends EGLMediaPlayerImpl { } @Override - public synchronized boolean playImpl() { + public synchronized boolean resumeImpl() { if(0==moviePtr) { return false; } @@ -155,6 +155,12 @@ public class OMXGLMediaPlayer extends EGLMediaPlayerImpl { return true; } + @Override + protected final void stopImpl() { + destroyImpl(); + initOMX(); + } + /** @return time position after issuing the command */ @Override protected int seekImpl(final int msec) { |