diff options
author | Sven Gothel <[email protected]> | 2013-12-11 20:46:04 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2013-12-11 20:46:04 +0100 |
commit | 8a8ed735f6631b2da7bf605c5c3dda4e0fc13905 (patch) | |
tree | 448cdba59bb96a5c926646071824609ae4002f97 | |
parent | a1be0f69bacb315e40a017b8997ef1c610da576e (diff) |
Bug 918 (2/2): Determine StreamWorker usage after stream-init ; Fix seek(..) ; Fallback for EOS Detection ; MovieSimple uses full GLEventListener for 'Audio Only' as well to test seek
Determine StreamWorker usage after init
- To support audio only files, we need to determine to use StreamWorker
after completion of stream-init.
Fix seek(..)
- FFMPeg: pos0 needs to use aPTS for audio-only
- Clip target time [0..duration[
Fallback for EOS Detection
In case the backend does not report proper EOS:
- Utilize 'nullFramesCount >= MAX' -> EOS,
where MAX is number of frames for 3s play duraction
and where 'nullFramesCount' is increased if no valid packet is available
and no decoded-video or -audio in the queue.
- Utilize pts > duration -> EOS
MovieSimple uses full GLEventListener for 'Audio Only' as well to test seek
- Matroska seek for audio-only leads to EOS ..
http://video.webmfiles.org/big-buck-bunny_trailer.webm
- MP4 audio-only seek works
http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4
MovieSimple/MovieCube:
- Use audio-pts in audio-only to calc target time
Tested:
- A, V and A+V
- Pause, Stop and Seek
- GNU/Linux
6 files changed, 310 insertions, 169 deletions
diff --git a/make/scripts/tests.sh b/make/scripts/tests.sh index e9d612f64..cb60028aa 100644 --- a/make/scripts/tests.sh +++ b/make/scripts/tests.sh @@ -141,7 +141,7 @@ function jrun() { #D_ARGS="-Djogl.debug.DebugGL -Djogl.debug.TraceGL -Djogl.debug.GLContext.TraceSwitch -Djogl.debug=all" #D_ARGS="-Djogamp.debug.IOUtil -Djogl.debug.GLSLCode -Djogl.debug.GLMediaPlayer" #D_ARGS="-Djogl.debug.GLMediaPlayer -Djogl.debug.AudioSink" - D_ARGS="-Djogl.debug.GLMediaPlayer -Djogl.debug.GLMediaPlayer.Native" + #D_ARGS="-Djogl.debug.GLMediaPlayer -Djogl.debug.GLMediaPlayer.Native" #D_ARGS="-Djogl.debug.GLMediaPlayer" #D_ARGS="-Djogl.debug.GLMediaPlayer.StreamWorker.delay=25 -Djogl.debug.GLMediaPlayer" #D_ARGS="-Djogl.debug.GLMediaPlayer.Native" 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 b0a645cbb..22a5cfb32 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java +++ b/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java @@ -358,7 +358,10 @@ public interface GLMediaPlayer extends TextureSequence { public void initStream(URI streamLoc, int vid, int aid, int textureCount) throws IllegalStateException, IllegalArgumentException; /** - * Returns the {@link StreamException} caught in the decoder thread, or <code>null</code>. + * Returns the {@link StreamException} caught in the decoder thread, or <code>null</code> if none occured. + * <p> + * Method clears the cached {@link StreamException}, hence an immediate subsequent call will return <code>null</code>. + * </p> * @see GLMediaEventListener#EVENT_CHANGE_ERR * @see StreamException */ diff --git a/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java b/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java index ae3f901a1..e6a0012d3 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java +++ b/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java @@ -138,6 +138,18 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { protected int displayedFrameCount = 0; protected volatile int video_pts_last = 0; + /** + * Help detect EOS, limit is {@link #MAX_FRAMELESS_MS_UNTIL_EOS}. + * To be used either by getNextTexture(..) or StreamWorker for audio-only. + */ + private int nullFrameCount = 0; + private int maxNullFrameCountUntilEOS = 0; + /** + * Help detect EOS, limit {@value} milliseconds without a valid frame. + */ + private static final int MAX_FRAMELESS_MS_UNTIL_EOS = 5000; + private static final int MAX_FRAMELESS_UNTIL_EOS_DEFAULT = MAX_FRAMELESS_MS_UNTIL_EOS / 30; // default value assuming 30fps + /** See {@link #getAudioSink()}. Set by implementation if used from within {@link #initStreamImpl(int, int)}! */ protected AudioSink audioSink = null; protected boolean audioSinkPlaySpeedSet = false; @@ -380,6 +392,12 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { if( null != streamWorker ) { streamWorker.doPause(); } + // Adjust target .. + if( msec >= duration ) { + msec = duration - (int)Math.floor(frame_duration); + } else if( msec < 0 ) { + msec = 0; + } pts1 = seekImpl(msec); resetAVPTSAndFlush(); if( null != audioSink && State.Playing == _state ) { @@ -509,6 +527,8 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { decodedFrameCount = 0; presentedFrameCount = 0; displayedFrameCount = 0; + nullFrameCount = 0; + maxNullFrameCountUntilEOS = MAX_FRAMELESS_UNTIL_EOS_DEFAULT; this.streamLoc = streamLoc; // Pre-parse for camera-input scheme @@ -530,20 +550,17 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { this.vid = vid; this.aid = aid; if ( this.streamLoc != null ) { - if( TEXTURE_COUNT_MIN < textureCount || STREAM_ID_NONE == vid ) { // Enable StreamWorker for 'audio only' as well (Bug 918). - streamWorker = new StreamWorker(); - } else { - new Thread() { - public void run() { - try { - initStreamImpl(vid, aid); - } catch (Throwable t) { - streamErr = new StreamException(t.getClass().getSimpleName()+" while initializing: "+GLMediaPlayerImpl.this.toString(), t); - changeState(GLMediaEventListener.EVENT_CHANGE_ERR, GLMediaPlayer.State.Uninitialized); - } // also initializes width, height, .. etc - } - }.start(); - } + new Thread() { + public void run() { + try { + // StreamWorker may be used, see API-doc of StreamWorker + initStreamImpl(vid, aid); + } catch (Throwable t) { + streamErr = new StreamException(t.getClass().getSimpleName()+" while initializing: "+GLMediaPlayerImpl.this.toString(), t); + changeState(GLMediaEventListener.EVENT_CHANGE_ERR, GLMediaPlayer.State.Uninitialized); + } // also initializes width, height, .. etc + } + }.start(); } } } @@ -748,6 +765,8 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { protected TextureFrame cachedFrame = null; protected long lastTimeMillis = 0; + private final boolean[] stGotVFrame = { false }; + @Override public final TextureFrame getNextTexture(GL gl) throws IllegalStateException { synchronized( stateLock ) { @@ -755,12 +774,9 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { throw new IllegalStateException("Instance not paused or playing: "+this); } if(State.Playing == state) { - TextureFrame nextFrame = null; boolean dropFrame = false; try { do { - final long currentTimeMillis; - final boolean playCached = null != cachedFrame; final boolean droppedFrame; if( dropFrame ) { presentedFrameCount--; @@ -769,24 +785,69 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { } else { droppedFrame = false; } + final boolean playCached = null != cachedFrame; + final int video_pts; + final boolean hasVideoFrame; + TextureFrame nextFrame; if( playCached ) { nextFrame = cachedFrame; cachedFrame = null; presentedFrameCount--; - } else if( STREAM_ID_NONE != vid ) { - if( null != videoFramesDecoded ) { // single threaded ? TEXTURE_COUNT_MIN == textureCount + video_pts = nextFrame.getPTS(); + hasVideoFrame = true; + } else { + if( null != videoFramesDecoded ) { + // multi-threaded and video available nextFrame = videoFramesDecoded.get(); + if( null != nextFrame ) { + video_pts = nextFrame.getPTS(); + hasVideoFrame = true; + } else { + video_pts = TimeFrameI.INVALID_PTS; + hasVideoFrame = false; + } } else { - nextFrame = getNextSingleThreaded(gl, lastFrame); + // single-threaded or audio-only + video_pts = getNextSingleThreaded(gl, lastFrame, stGotVFrame); + nextFrame = lastFrame; + hasVideoFrame = stGotVFrame[0]; } } - currentTimeMillis = Platform.currentTimeMillis(); - if( null != nextFrame ) { - presentedFrameCount++; - final int video_pts = nextFrame.getPTS(); - if( video_pts == TimeFrameI.END_OF_STREAM_PTS ) { - pauseImpl(true, GLMediaEventListener.EVENT_CHANGE_EOS); - } else if( video_pts != TimeFrameI.INVALID_PTS ) { + final long currentTimeMillis = Platform.currentTimeMillis(); + + if( TimeFrameI.END_OF_STREAM_PTS == video_pts || + duration <= video_pts || maxNullFrameCountUntilEOS <= nullFrameCount ) + { + // EOS + if( DEBUG ) { + System.err.println( "AV-EOS (getNextTexture): EOS_PTS "+(TimeFrameI.END_OF_STREAM_PTS == video_pts)+", "+this); + } + pauseImpl(true, GLMediaEventListener.EVENT_CHANGE_EOS); + + } else if( TimeFrameI.INVALID_PTS == video_pts ) { // no audio or video frame + if( null == videoFramesDecoded || !videoFramesDecoded.isEmpty() ) { + nullFrameCount++; + } + if( DEBUG ) { + final int audio_pts = getAudioPTSImpl(); + final int audio_scr = (int) ( ( currentTimeMillis - audio_scr_t0 ) * playSpeed ); + final int d_apts; + if( audio_pts != TimeFrameI.INVALID_PTS ) { + d_apts = audio_pts - audio_scr; + } else { + d_apts = 0; + } + final int video_scr = video_scr_pts + (int) ( ( currentTimeMillis - video_scr_t0 ) * playSpeed ); + final int d_vpts = video_pts - video_scr; + System.err.println( "AV~: dT "+(currentTimeMillis-lastTimeMillis)+", nullFrames "+nullFrameCount+ + getPerfStringImpl( video_scr, video_pts, d_vpts, audio_scr, audio_pts, d_apts, 0 ) + ", droppedFrame "+droppedFrame); + } + } else { // valid pts: has audio or video frame + nullFrameCount=0; + + if( hasVideoFrame ) { // has video frame + presentedFrameCount++; + final int audio_pts = getAudioPTSImpl(); final int audio_scr = (int) ( ( currentTimeMillis - audio_scr_t0 ) * playSpeed ); final int d_apts; @@ -840,29 +901,16 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { ", avg dpy-fps "+avg_dpy_duration+" ms/f, maxD "+maxVideoDelay+" ms, "+_nextFrame+", playCached " + playCached + ", dropFrame "+dropFrame); } } - } else if( DEBUG ) { - System.err.println("Invalid PTS: "+nextFrame); - } - if( null != nextFrame && null != videoFramesFree ) { - // Had frame and not single threaded ? (TEXTURE_COUNT_MIN < textureCount) - final TextureFrame _lastFrame = lastFrame; - lastFrame = nextFrame; + } // has video frame + } // has audio or video frame + + if( null != videoFramesFree && null != nextFrame ) { + // Had frame and not single threaded ? (TEXTURE_COUNT_MIN < textureCount) + final TextureFrame _lastFrame = lastFrame; + lastFrame = nextFrame; + if( null != _lastFrame ) { videoFramesFree.putBlocking(_lastFrame); } - } else if( DEBUG ) { - final int video_pts = lastFrame.getPTS(); - final int audio_pts = getAudioPTSImpl(); - final int audio_scr = (int) ( ( currentTimeMillis - audio_scr_t0 ) * playSpeed ); - final int d_apts; - if( audio_pts != TimeFrameI.INVALID_PTS ) { - d_apts = audio_pts - audio_scr; - } else { - d_apts = 0; - } - final int video_scr = video_scr_pts + (int) ( ( currentTimeMillis - video_scr_t0 ) * playSpeed ); - final int d_vpts = video_pts - video_scr; - System.err.println( "AV~: dT "+(currentTimeMillis-lastTimeMillis)+", "+ - getPerfStringImpl( video_scr, video_pts, d_vpts, audio_scr, audio_pts, d_apts, 0 ) + ", droppedFrame "+droppedFrame); } lastTimeMillis = currentTimeMillis; } while( dropFrame ); @@ -899,24 +947,24 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { */ protected abstract int getNextTextureImpl(GL gl, TextureFrame nextFrame); - protected final TextureFrame getNextSingleThreaded(final GL gl, final TextureFrame nextFrame) throws InterruptedException { + protected final int getNextSingleThreaded(final GL gl, final TextureFrame nextFrame, boolean[] gotVFrame) throws InterruptedException { + final int pts; if( STREAM_ID_NONE != vid ) { preNextTextureImpl(gl); - final int vPTS = getNextTextureImpl(gl, nextFrame); + pts = getNextTextureImpl(gl, nextFrame); postNextTextureImpl(gl); - if( TimeFrameI.INVALID_PTS != vPTS ) { + if( TimeFrameI.INVALID_PTS != pts ) { newFrameAvailable(nextFrame, Platform.currentTimeMillis()); - return nextFrame; + gotVFrame[0] = true; + } else { + gotVFrame[0] = false; } } else { // audio only - final int vPTS = getNextTextureImpl(null, null); - if( TimeFrameI.INVALID_PTS != vPTS && TimeFrameI.END_OF_STREAM_PTS == vPTS ) { - // state transition incl. notification - pauseImpl(true, GLMediaEventListener.EVENT_CHANGE_EOS); - } + pts = getNextTextureImpl(null, null); + gotVFrame[0] = false; } - return null; + return pts; } @@ -964,6 +1012,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { } } private void resetAVPTS() { + nullFrameCount = 0; presentedFrameCount = 0; displayedFrameCount = 0; decodedFrameCount = 0; @@ -986,6 +1035,11 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { } } + /** + * After {@link GLMediaPlayerImpl#initStreamImpl(int, int) initStreamImpl(..)} is completed via + * {@link GLMediaPlayerImpl#updateAttributes(int, int, int, int, int, int, int, float, int, int, int, String, String) updateAttributes(..)}, + * the latter decides whether StreamWorker is being used. + */ class StreamWorker extends Thread { private volatile boolean isRunning = false; private volatile boolean isActive = false; @@ -1000,14 +1054,23 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { /** * Starts this daemon thread, - * which initializes the stream first via {@link GLMediaPlayerImpl#initStreamImpl(int, int)} first. * <p> - * After stream initialization, this thread pauses! + * This thread pauses after it's started! * </p> **/ StreamWorker() { setDaemon(true); - start(); + synchronized(this) { + start(); + while( !isRunning ) { + this.notifyAll(); // wake-up startup-block + try { + this.wait(); // wait until started + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } } private void makeCurrent(GLContext ctx) { @@ -1113,17 +1176,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { synchronized ( this ) { isRunning = true; - try { - isBlocked = true; - initStreamImpl(vid, aid); - isBlocked = false; - } catch (Throwable t) { - streamErr = new StreamException(t.getClass().getSimpleName()+" while initializing: "+GLMediaPlayerImpl.this.toString(), t); - isBlocked = false; - isRunning = false; - changeState(GLMediaEventListener.EVENT_CHANGE_ERR, GLMediaPlayer.State.Uninitialized); - return; // end of thread! - } // also initializes width, height, .. etc + this.notifyAll(); // wake-up ctor() } while( !shallStop ){ @@ -1179,6 +1232,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { } isBlocked = false; final int vPTS = getNextTextureImpl(gl, nextFrame); + boolean audioEOS = false; if( TimeFrameI.INVALID_PTS != vPTS ) { if( null != nextFrame ) { if( STREAM_WORKER_DELAY > 0 ) { @@ -1191,16 +1245,30 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { nextFrame = null; } else { // audio only - if( TimeFrameI.END_OF_STREAM_PTS == vPTS ) { - // state transition incl. notification - synchronized ( this ) { - shallPause = true; - isActive = false; - this.notifyAll(); // wake-up potential do*() - } - pauseImpl(true, GLMediaEventListener.EVENT_CHANGE_EOS); + if( TimeFrameI.END_OF_STREAM_PTS == vPTS || duration < vPTS ) { + audioEOS = true; + } else { + nullFrameCount = 0; } } + } else if( null == nextFrame ) { + // audio only + audioEOS = maxNullFrameCountUntilEOS <= nullFrameCount; + if( null == audioSink || 0 == audioSink.getEnqueuedFrameCount() ) { + nullFrameCount++; + } + } + if( audioEOS ) { + // state transition incl. notification + synchronized ( this ) { + shallPause = true; + isActive = false; + this.notifyAll(); // wake-up potential do*() + } + if( DEBUG ) { + System.err.println( "AV-EOS (StreamWorker): EOS_PTS "+(TimeFrameI.END_OF_STREAM_PTS == vPTS)+", "+GLMediaPlayerImpl.this); + } + pauseImpl(true, GLMediaEventListener.EVENT_CHANGE_EOS); } } catch (InterruptedException e) { isBlocked = false; @@ -1242,7 +1310,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { } } static int StreamWorkerInstanceId = 0; - private StreamWorker streamWorker = null; + private volatile StreamWorker streamWorker = null; private volatile StreamException streamErr = null; protected final int addStateEventMask(int event_mask, State newState) { @@ -1288,7 +1356,9 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { int bps_video, int bps_audio, float fps, int videoFrames, int audioFrames, int duration, String vcodec, String acodec) { int event_mask = 0; - if( state == State.Uninitialized ) { + final boolean wasUninitialized = state == State.Uninitialized; + + if( wasUninitialized ) { event_mask |= GLMediaEventListener.EVENT_CHANGE_INIT; state = State.Initialized; } @@ -1314,7 +1384,13 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { if( this.fps != fps ) { event_mask |= GLMediaEventListener.EVENT_CHANGE_FPS; this.fps = fps; - this.frame_duration = 1000f / fps; + if( 0 != fps ) { + this.frame_duration = 1000f / fps; + this.maxNullFrameCountUntilEOS = MAX_FRAMELESS_MS_UNTIL_EOS / (int)this.frame_duration; + } else { + this.frame_duration = 0; + this.maxNullFrameCountUntilEOS = MAX_FRAMELESS_UNTIL_EOS_DEFAULT; + } } if( this.bps_stream != bps_stream || this.bps_video != bps_video || this.bps_audio != bps_audio ) { event_mask |= GLMediaEventListener.EVENT_CHANGE_BPS; @@ -1339,6 +1415,17 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { if(0==event_mask) { 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( DEBUG ) { + System.err.println("XXX Initialize @ updateAttributes: "+this); + } + } attributesUpdated(event_mask); } @@ -1434,9 +1521,9 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { final int decVideoFrames = null != videoFramesDecoded ? videoFramesDecoded.size() : 0; final int video_scr = video_scr_pts + (int) ( ( Platform.currentTimeMillis() - video_scr_t0 ) * playSpeed ); final String camPath = null != cameraPath ? ", camera: "+cameraPath : ""; - return "GLMediaPlayer["+state+", vSCR "+video_scr+", frames[p "+presentedFrameCount+", d "+decodedFrameCount+", t "+videoFrames+" ("+tt+" s)], "+ - "speed "+playSpeed+", "+bps_stream+" bps, "+ - "Texture[count "+textureCount+", free "+freeVideoFrames+", dec "+decVideoFrames+", tagt "+toHexString(textureTarget)+", ifmt "+toHexString(textureInternalFormat)+", fmt "+toHexString(textureFormat)+", type "+toHexString(textureType)+"], "+ + return "GLMediaPlayer["+state+", vSCR "+video_scr+", frames[p "+presentedFrameCount+", d "+decodedFrameCount+", t "+videoFrames+" ("+tt+" s), z "+nullFrameCount+" / "+maxNullFrameCountUntilEOS+"], "+ + "speed "+playSpeed+", "+bps_stream+" bps, hasSW "+(null!=streamWorker)+ + ", Texture[count "+textureCount+", free "+freeVideoFrames+", dec "+decVideoFrames+", tagt "+toHexString(textureTarget)+", ifmt "+toHexString(textureInternalFormat)+", fmt "+toHexString(textureFormat)+", type "+toHexString(textureType)+"], "+ "Video[id "+vid+", <"+vcodec+">, "+width+"x"+height+", glOrient "+isInGLOrientation+", "+fps+" fps, "+frame_duration+" fdur, "+bps_video+" bps], "+ "Audio[id "+aid+", <"+acodec+">, "+bps_audio+" bps, "+audioFrames+" frames], uri "+loc+camPath+"]"; } @@ -1470,7 +1557,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { freeVideoFrames = 0; decVideoFrames = 0; } - return state+", frames[(p "+presentedFrameCount+", d "+decodedFrameCount+") / "+videoFrames+", "+tt+" s], "+ + return state+", frames[(p "+presentedFrameCount+", d "+decodedFrameCount+") / "+videoFrames+", "+tt+" s, z "+nullFrameCount+" / "+maxNullFrameCountUntilEOS+"], "+ "speed " + playSpeed+", dAV "+( d_vpts - d_apts )+", vSCR "+video_scr+", vpts "+video_pts+", dSCR["+d_vpts+", avrg "+video_dpts_avg_diff+"], "+ "aSCR "+audio_scr+", apts "+audio_pts+" ( "+d_apts+" ), "+audioSinkInfo+ ", Texture[count "+textureCount+", free "+freeVideoFrames+", dec "+decVideoFrames+"]"; diff --git a/src/jogl/native/libav/ffmpeg_impl_template.c b/src/jogl/native/libav/ffmpeg_impl_template.c index b46586acc..44acfe46a 100644 --- a/src/jogl/native/libav/ffmpeg_impl_template.c +++ b/src/jogl/native/libav/ffmpeg_impl_template.c @@ -1144,6 +1144,11 @@ JNIEXPORT jint JNICALL FF_FUNC(readNextPacket0) pkt_odata = packet.data; pkt_osize = packet.size; if( AVERROR_EOF == avRes || ( pAV->pFormatCtx->pb && pAV->pFormatCtx->pb->eof_reached ) ) { + if( pAV->verbose ) { + fprintf(stderr, "EOS: avRes[res %d, eos %d], pb-EOS %d\n", + avRes, AVERROR_EOF == avRes, + ( pAV->pFormatCtx->pb && pAV->pFormatCtx->pb->eof_reached ) ); + } resPTS = END_OF_STREAM_PTS; } else if( 0 <= avRes ) { if( pAV->verbose ) { @@ -1475,15 +1480,16 @@ JNIEXPORT jint JNICALL FF_FUNC(seek0) (JNIEnv *env, jobject instance, jlong ptr, jint pos1) { const FFMPEGToolBasicAV_t *pAV = (FFMPEGToolBasicAV_t *)((void *)((intptr_t)ptr)); - const int64_t pos0 = pAV->vPTS; - int64_t pts0; + int64_t pos0, pts0; int streamID; AVRational time_base; if( pAV->vid >= 0 ) { + pos0 = pAV->vPTS; streamID = pAV->vid; time_base = pAV->pVStream->time_base; pts0 = pAV->pVFrame->pkt_pts; } else if( pAV->aid >= 0 ) { + pos0 = pAV->aPTS; streamID = pAV->aid; time_base = pAV->pAStream->time_base; pts0 = pAV->pAFrames[pAV->aFrameCurrent]->pkt_pts; @@ -1493,16 +1499,16 @@ JNIEXPORT jint JNICALL FF_FUNC(seek0) int64_t pts1 = (int64_t) (pos1 * (int64_t) time_base.den) / (1000 * (int64_t) time_base.num); if(pAV->verbose) { - fprintf(stderr, "SEEK: vid %d, aid %d, pos1 %d, pts: %"PRId64" -> %"PRId64"\n", pAV->vid, pAV->aid, pos1, pts0, pts1); + fprintf(stderr, "SEEK: vid %d, aid %d, pos0 %d, pos1 %d, pts: %"PRId64" -> %"PRId64"\n", pAV->vid, pAV->aid, pos0, pos1, pts0, pts1); } int flags = 0; if(pos1 < pos0) { flags |= AVSEEK_FLAG_BACKWARD; } - int res; + int res = -2; if(HAS_FUNC(sp_av_seek_frame)) { if(pAV->verbose) { - fprintf(stderr, "SEEK.0: pre : s %"PRId64" / %"PRId64" -> t %d / %"PRId64"\n", pos0, pts0, pos1, pts1); + fprintf(stderr, "SEEK.0: pre : s %d / %"PRId64" -> t %d / %"PRId64"\n", pos0, pts0, pos1, pts1); } sp_av_seek_frame(pAV->pFormatCtx, streamID, pts1, flags); } else if(HAS_FUNC(sp_avformat_seek_file)) { diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieCube.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieCube.java index 8f27e19c4..c1ccf7c39 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieCube.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieCube.java @@ -153,7 +153,7 @@ public class MovieCube implements GLEventListener { return; } System.err.println("MC "+e); - int pts0 = mPlayer.getVideoPTS(); + final int pts0 = GLMediaPlayer.STREAM_ID_NONE != mPlayer.getVID() ? mPlayer.getVideoPTS() : mPlayer.getAudioPTS(); int pts1 = 0; switch(e.getKeyCode()) { case KeyEvent.VK_RIGHT: pts1 = pts0 + 1000; break; diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSimple.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSimple.java index 1a9914bb7..ddf5c709c 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSimple.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSimple.java @@ -150,7 +150,8 @@ public class MovieSimple implements GLEventListener { if(y>winHeight/2) { final float dp = (float)(x-prevMouseX)/(float)winWidth; - mPlayer.seek(mPlayer.getVideoPTS() + (int) (mPlayer.getDuration() * dp)); + final int pts0 = GLMediaPlayer.STREAM_ID_NONE != mPlayer.getVID() ? mPlayer.getVideoPTS() : mPlayer.getAudioPTS(); + mPlayer.seek(pts0 + (int) (mPlayer.getDuration() * dp)); } else { mPlayer.play(); rotate = 1; @@ -174,7 +175,7 @@ public class MovieSimple implements GLEventListener { return; } System.err.println("MC "+e); - int pts0 = mPlayer.getVideoPTS(); + final int pts0 = GLMediaPlayer.STREAM_ID_NONE != mPlayer.getVID() ? mPlayer.getVideoPTS() : mPlayer.getAudioPTS(); int pts1 = 0; switch(e.getKeyCode()) { case KeyEvent.VK_RIGHT: pts1 = pts0 + 1000; break; @@ -372,9 +373,7 @@ public class MovieSimple implements GLEventListener { if( GLMediaPlayer.State.Uninitialized == mPlayer.getState() ) { throw new IllegalStateException("mPlayer in uninitialized state: "+mPlayer); } - if( GLMediaPlayer.STREAM_ID_NONE == mPlayer.getVID() ) { - throw new IllegalStateException("mPlayer has no VID/stream selected: "+mPlayer); - } + final boolean hasVideo = GLMediaPlayer.STREAM_ID_NONE != mPlayer.getVID(); resetGLState = false; zoom0 = orthoProjection ? 0f : -2.5f; @@ -397,10 +396,20 @@ public class MovieSimple implements GLEventListener { } System.out.println("p1 "+mPlayer+", shared "+mPlayerShared); final TextureFrame frame = mPlayer.getLastTexture(); - if( null == frame ) { - throw new InternalError("XXX: "+mPlayer); + if( null != frame ) { + if( !hasVideo ) { + throw new InternalError("XXX: "+mPlayer); + } + tex = frame.getTexture(); + if( null == tex ) { + throw new InternalError("XXX: "+mPlayer); + } + } else { + tex = null; + if( hasVideo ) { + throw new InternalError("XXX: "+mPlayer); + } } - tex = mPlayer.getLastTexture().getTexture(); if(!mPlayerShared) { mPlayer.setTextureMinMagFilter( new int[] { GL.GL_NEAREST, GL.GL_LINEAR } ); } @@ -413,74 +422,76 @@ public class MovieSimple implements GLEventListener { throw new GLException(glex); } - initShader(gl); + if( hasVideo ) { + initShader(gl); - // Push the 1st uniform down the path - st.useProgram(gl, true); + // Push the 1st uniform down the path + st.useProgram(gl, true); - int[] viewPort = new int[] { 0, 0, drawable.getWidth(), drawable.getHeight()}; - pmvMatrix = new PMVMatrix(); - reshapePMV(viewPort[2], viewPort[3]); - pmvMatrixUniform = new GLUniformData("mgl_PMVMatrix", 4, 4, pmvMatrix.glGetPMvMatrixf()); - if(!st.uniform(gl, pmvMatrixUniform)) { - throw new GLException("Error setting PMVMatrix in shader: "+st); - } - if(!st.uniform(gl, new GLUniformData("mgl_ActiveTexture", mPlayer.getTextureUnit()))) { - throw new GLException("Error setting mgl_ActiveTexture in shader: "+st); - } + int[] viewPort = new int[] { 0, 0, drawable.getWidth(), drawable.getHeight()}; + pmvMatrix = new PMVMatrix(); + reshapePMV(viewPort[2], viewPort[3]); + pmvMatrixUniform = new GLUniformData("mgl_PMVMatrix", 4, 4, pmvMatrix.glGetPMvMatrixf()); + if(!st.uniform(gl, pmvMatrixUniform)) { + throw new GLException("Error setting PMVMatrix in shader: "+st); + } + if(!st.uniform(gl, new GLUniformData("mgl_ActiveTexture", mPlayer.getTextureUnit()))) { + throw new GLException("Error setting mgl_ActiveTexture in shader: "+st); + } - float dWidth = drawable.getWidth(); - float dHeight = drawable.getHeight(); - float mWidth = mPlayer.getWidth(); - float mHeight = mPlayer.getHeight(); - float mAspect = mWidth/mHeight; - System.err.println("XXX0: mov aspect: "+mAspect); - float xs, ys; - if(orthoProjection) { - if(mPlayerScaleOrig && mWidth < dWidth && mHeight < dHeight) { - xs = mWidth/2f; ys = xs / mAspect; + float dWidth = drawable.getWidth(); + float dHeight = drawable.getHeight(); + float mWidth = mPlayer.getWidth(); + float mHeight = mPlayer.getHeight(); + float mAspect = mWidth/mHeight; + System.err.println("XXX0: mov aspect: "+mAspect); + float xs, ys; + if(orthoProjection) { + if(mPlayerScaleOrig && mWidth < dWidth && mHeight < dHeight) { + xs = mWidth/2f; ys = xs / mAspect; + } else { + xs = dWidth/2f; ys = xs / mAspect; // w>h + } } else { - xs = dWidth/2f; ys = xs / mAspect; // w>h + if(mPlayerScaleOrig && mWidth < dWidth && mHeight < dHeight) { + xs = mAspect * ( mWidth / dWidth ) ; ys = xs / mAspect ; + } else { + xs = mAspect; ys = 1f; // b>h + } } - } else { - if(mPlayerScaleOrig && mWidth < dWidth && mHeight < dHeight) { - xs = mAspect * ( mWidth / dWidth ) ; ys = xs / mAspect ; - } else { - xs = mAspect; ys = 1f; // b>h + verts = new float[] { -1f*xs, -1f*ys, 0f, // LB + 1f*xs, 1f*ys, 0f // RT + }; + { + System.err.println("XXX0: pixel LB: "+verts[0]+", "+verts[1]+", "+verts[2]); + System.err.println("XXX0: pixel RT: "+verts[3]+", "+verts[4]+", "+verts[5]); + float[] winLB = new float[3]; + float[] winRT = new float[3]; + pmvMatrix.gluProject(verts[0], verts[1], verts[2], viewPort, 0, winLB, 0); + pmvMatrix.gluProject(verts[3], verts[4], verts[5], viewPort, 0, winRT, 0); + System.err.println("XXX0: win LB: "+winLB[0]+", "+winLB[1]+", "+winLB[2]); + System.err.println("XXX0: win RT: "+winRT[0]+", "+winRT[1]+", "+winRT[2]); } - } - verts = new float[] { -1f*xs, -1f*ys, 0f, // LB - 1f*xs, 1f*ys, 0f // RT - }; - { - System.err.println("XXX0: pixel LB: "+verts[0]+", "+verts[1]+", "+verts[2]); - System.err.println("XXX0: pixel RT: "+verts[3]+", "+verts[4]+", "+verts[5]); - float[] winLB = new float[3]; - float[] winRT = new float[3]; - pmvMatrix.gluProject(verts[0], verts[1], verts[2], viewPort, 0, winLB, 0); - pmvMatrix.gluProject(verts[3], verts[4], verts[5], viewPort, 0, winRT, 0); - System.err.println("XXX0: win LB: "+winLB[0]+", "+winLB[1]+", "+winLB[2]); - System.err.println("XXX0: win RT: "+winRT[0]+", "+winRT[1]+", "+winRT[2]); - } - interleavedVBO = GLArrayDataServer.createGLSLInterleaved(3+4+2, GL.GL_FLOAT, false, 3*4, GL.GL_STATIC_DRAW); - { - interleavedVBO.addGLSLSubArray("mgl_Vertex", 3, GL.GL_ARRAY_BUFFER); - interleavedVBO.addGLSLSubArray("mgl_Color", 4, GL.GL_ARRAY_BUFFER); - interleavedVBO.addGLSLSubArray("mgl_MultiTexCoord", 2, GL.GL_ARRAY_BUFFER); - } - updateInterleavedVBO(gl, tex); + interleavedVBO = GLArrayDataServer.createGLSLInterleaved(3+4+2, GL.GL_FLOAT, false, 3*4, GL.GL_STATIC_DRAW); + { + interleavedVBO.addGLSLSubArray("mgl_Vertex", 3, GL.GL_ARRAY_BUFFER); + interleavedVBO.addGLSLSubArray("mgl_Color", 4, GL.GL_ARRAY_BUFFER); + interleavedVBO.addGLSLSubArray("mgl_MultiTexCoord", 2, GL.GL_ARRAY_BUFFER); + } + updateInterleavedVBO(gl, tex); - st.ownAttribute(interleavedVBO, true); - gl.glClearColor(0.3f, 0.3f, 0.3f, 0.3f); + st.ownAttribute(interleavedVBO, true); + gl.glClearColor(0.3f, 0.3f, 0.3f, 0.3f); - gl.glEnable(GL2ES2.GL_DEPTH_TEST); + gl.glEnable(GL2ES2.GL_DEPTH_TEST); - st.useProgram(gl, false); + st.useProgram(gl, false); - // Let's show the completed shader state .. - System.out.println("iVBO: "+interleavedVBO); - System.out.println(st); + // Let's show the completed shader state .. + System.out.println("iVBO: "+interleavedVBO); + System.out.println(st); + } if(!mPlayerShared) { mPlayer.play(); @@ -697,6 +708,7 @@ public class MovieSimple implements GLEventListener { int textureCount = 3; // default - threaded boolean ortho = true; boolean zoom = false; + boolean _loopEOS = false; boolean forceES2 = false; boolean forceES3 = false; @@ -742,6 +754,8 @@ public class MovieSimple implements GLEventListener { ortho=false; } else if(args[i].equals("-zoom")) { zoom=true; + } else if(args[i].equals("-loop")) { + _loopEOS=true; } else if(args[i].equals("-url")) { i++; url_s = args[i]; @@ -757,6 +771,7 @@ public class MovieSimple implements GLEventListener { } origSize = _origSize; } + final boolean loopEOS = _loopEOS; final URI streamLoc; if( null != url_s ) { streamLoc = new URI(url_s); @@ -827,6 +842,7 @@ public class MovieSimple implements GLEventListener { System.err.println("MovieSimple AttributesChanges: events_mask 0x"+Integer.toHexString(event_mask)+", when "+when); System.err.println("MovieSimple State: "+mp); if( 0 != ( GLMediaEventListener.EVENT_CHANGE_SIZE & event_mask ) ) { + System.err.println("MovieSimple State: CHANGE_SIZE"); if( origSize ) { window.setSize(mp.getWidth(), mp.getHeight()); } @@ -834,11 +850,14 @@ public class MovieSimple implements GLEventListener { ms.resetGLState(); } if( 0 != ( GLMediaEventListener.EVENT_CHANGE_INIT & event_mask ) ) { - if( GLMediaPlayer.STREAM_ID_NONE != ms.mPlayer.getVID() ) { - window.addGLEventListener(ms); - anim.setUpdateFPSFrames(60, System.err); - anim.resetFPSCounter(); - } else { + System.err.println("MovieSimple State: INIT"); + // Use GLEventListener in all cases [A+V, V, A] + window.addGLEventListener(ms); + anim.setUpdateFPSFrames(60, System.err); + anim.resetFPSCounter(); + /** + * Kick off player w/o GLEventListener, i.e. for audio only. + * try { ms.mPlayer.initGL(null); } catch (Exception e) { @@ -848,12 +867,38 @@ public class MovieSimple implements GLEventListener { } ms.mPlayer.play(); System.out.println("play.1 "+ms.mPlayer); + */ + } + boolean destroy = false; + Throwable err = null; + + if( 0 != ( GLMediaEventListener.EVENT_CHANGE_EOS & event_mask ) ) { + err = ms.mPlayer.getStreamException(); + if( null != err ) { + System.err.println("MovieSimple State: EOS + Exception"); + destroy = true; + } else { + System.err.println("MovieSimple State: EOS"); + if( loopEOS ) { + ms.mPlayer.seek(0); + ms.mPlayer.play(); + } else { + destroy = true; + } + } + } + if( 0 != ( GLMediaEventListener.EVENT_CHANGE_ERR & event_mask ) ) { + err = ms.mPlayer.getStreamException(); + if( null != err ) { + System.err.println("MovieSimple State: ERR + Exception"); + } else { + System.err.println("MovieSimple State: ERR"); } + destroy = true; } - if( 0 != ( ( GLMediaEventListener.EVENT_CHANGE_ERR | GLMediaEventListener.EVENT_CHANGE_EOS ) & event_mask ) ) { - final StreamException se = ms.mPlayer.getStreamException(); - if( null != se ) { - se.printStackTrace(); + if( destroy ) { + if( null != err ) { + err.printStackTrace(); } destroyWindow(); } |