From d0e01cb5c0ec3e48b8a9b9b79a7795b214c6e3ea Mon Sep 17 00:00:00 2001
From: Sven Gothel
- * Variable type, value range and dimension has been chosen to suit embedded CPUs - * and characteristics of audio and video streaming. - * Milliseconds of type integer with a maximum value of {@link Integer#MAX_VALUE} - * will allow tracking time up 2,147,483.647 seconds or - * 24 days 20 hours 31 minutes and 23 seconds. - * Milliseconds granularity is also more than enough to deal with A-V synchronization, - * where the threshold usually lies within 22ms. + *
+ * Timestamp type and value range has been chosen to suit embedded CPUs + * and characteristics of audio and video streaming. See {@link TimeFrameI}. *
* *
@@ -109,35 +111,21 @@ public interface TextureSequence {
* Texture holder interface, maybe specialized by implementation
* to associated related data.
*/
- public static class TextureFrame {
- /** Constant marking an invalid PTS, i.e. Integer.MIN_VALUE == 0x80000000 == {@value}. Sync w/ native code. */
- public static final int INVALID_PTS = 0x80000000;
-
- /** Constant marking the end of the stream PTS, i.e. Integer.MIN_VALUE - 1 == 0x7FFFFFFF == {@value}. Sync w/ native code. */
- public static final int END_OF_STREAM_PTS = 0x7FFFFFFF;
-
+ public static class TextureFrame extends TimeFrameI {
+ public TextureFrame(Texture t, int pts, int duration) {
+ super(pts, duration);
+ texture = t;
+ }
public TextureFrame(Texture t) {
texture = t;
- pts = INVALID_PTS;
- duration = 0;
}
public final Texture getTexture() { return texture; }
- /** Get this frame's presentation timestamp (PTS) in milliseconds. */
- public final int getPTS() { return pts; }
- /** Set this frame's presentation timestamp (PTS) in milliseconds. */
- public final void setPTS(int pts) { this.pts = pts; }
- /** Get this frame's duration in milliseconds. */
- public final int getDuration() { return duration; }
- /** Set this frame's duration in milliseconds. */
- public final void setDuration(int duration) { this.duration = duration; }
public String toString() {
- return "TextureFrame[pts " + pts + " ms, l " + duration + " ms, texID "+ texture.getTextureObject() + "]";
+ return "TextureFrame[pts " + pts + " ms, l " + duration + " ms, texID "+ (null != texture ? texture.getTextureObject() : 0) + "]";
}
protected final Texture texture;
- protected int pts;
- protected int duration;
}
public interface TexSeqEventListener
+ * Shall also take care of {@link AudioSink} initialization if appropriate. + *
+ * @param gl null for audio-only, otherwise a valid and current GL object. + * @throws IOException + * @throws GLException + */ protected abstract void initGLImpl(GL gl) throws IOException, GLException; /** @@ -570,12 +583,12 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { } private final void removeAllTextureFrames(GL gl) { - if( null != videoFramesOrig ) { - final TextureFrame[] texFrames = videoFramesOrig; - videoFramesOrig = null; - videoFramesFree = null; - videoFramesDecoded = null; - lastFrame = null; + final TextureFrame[] texFrames = videoFramesOrig; + videoFramesOrig = null; + videoFramesFree = null; + videoFramesDecoded = null; + lastFrame = null; + if( null != texFrames ) { for(int i=0; i+ * Video frames shall be ignored, if {@link #getVID()} is {@link #STREAM_ID_NONE}. + *
+ *+ * Audio frames shall be ignored, if {@link #getAID()} is {@link #STREAM_ID_NONE}. + *
+ *+ * Methods is invoked on the StreamWorker decoding thread. + *
+ ** Implementation shall care of OpenGL synchronization as required, e.g. glFinish()/glFlush()! - * @param gl - * @param nextFrame - * @return + *
+ * @param gl valid and current GL instance, shall benull
for audio only.
+ * @param nextFrame the {@link TextureFrame} to store the video PTS and texture data,
+ * shall be null
for audio only.
+ * @return the last processed video PTS value, maybe {@link TimeFrameI#INVALID_PTS} if video frame is invalid or n/a.
+ * Will be {@link TimeFrameI#END_OF_STREAM_PTS} if end of stream reached.
*/
- protected abstract boolean getNextTextureImpl(GL gl, TextureFrame nextFrame);
+ protected abstract int getNextTextureImpl(GL gl, TextureFrame nextFrame);
/**
* {@inheritDoc}
@@ -731,12 +779,12 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
private void flushAllVideoFrames() {
if( null != videoFramesFree ) {
videoFramesFree.resetFull(videoFramesOrig);
+ lastFrame = videoFramesFree.get();
+ if( null == lastFrame ) { throw new InternalError("XXX"); }
}
if( null != videoFramesDecoded ) {
videoFramesDecoded.clear();
}
- lastFrame = videoFramesFree.get( );
- if( null == lastFrame ) { throw new InternalError("XXX"); }
cachedFrame = null;
}
private void resetAllAudioVideoSync() {
@@ -842,14 +890,16 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
public synchronized void doPause() {
if( isActive ) {
shallPause = true;
- if( isBlocked && isActive ) {
- this.interrupt();
- }
- while( isActive ) {
- try {
- this.wait(); // wait until paused
- } catch (InterruptedException e) {
- e.printStackTrace();
+ if( Thread.currentThread() != this ) {
+ if( isBlocked && isActive ) {
+ this.interrupt();
+ }
+ while( isActive ) {
+ try {
+ this.wait(); // wait until paused
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
}
}
}
@@ -857,12 +907,14 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
public synchronized void doResume() {
if( isRunning && !isActive ) {
shallPause = false;
- while( !isActive ) {
- this.notify(); // wake-up pause-block
- try {
- this.wait(); // wait until resumed
- } catch (InterruptedException e) {
- e.printStackTrace();
+ if( Thread.currentThread() != this ) {
+ while( !isActive ) {
+ this.notify(); // wake-up pause-block
+ try {
+ this.wait(); // wait until resumed
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
}
}
}
@@ -870,15 +922,17 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
public synchronized void doStop() {
if( isRunning ) {
shallStop = true;
- if( isBlocked && isRunning ) {
- this.interrupt();
- }
- while( isRunning ) {
- this.notify(); // wake-up pause-block (opt)
- try {
- this.wait(); // wait until stopped
- } catch (InterruptedException e) {
- e.printStackTrace();
+ if( Thread.currentThread() != this ) {
+ if( isBlocked && isRunning ) {
+ this.interrupt();
+ }
+ while( isRunning ) {
+ this.notify(); // wake-up pause-block (opt)
+ try {
+ this.wait(); // wait until stopped
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
}
}
}
@@ -939,51 +993,68 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
preNextTextureImpl(sharedGLCtx.getGL());
sharedGLCtxCurrent = true;
}
+ if( null == videoFramesFree ) {
+ throw new InternalError("XXX videoFramesFree is null");
+ }
}
}
if( !shallStop ) {
TextureFrame nextFrame = null;
- if( null != sharedGLCtx ) {
- try {
- isBlocked = true;
- nextFrame = videoFramesFree.getBlocking( );
- isBlocked = false;
- nextFrame.setPTS( TextureFrame.INVALID_PTS ); // mark invalid until processed!
- final GL gl = sharedGLCtxCurrent ? sharedGLCtx.getGL() : null;
- if( getNextTextureImpl(gl, nextFrame) ) {
+ try {
+ final GL gl;
+ isBlocked = true;
+ if( null != videoFramesFree ) {
+ nextFrame = videoFramesFree.getBlocking();
+ nextFrame.setPTS( TimeFrameI.INVALID_PTS ); // mark invalid until processed!
+ gl = sharedGLCtx.getGL();
+ } else {
+ gl = null;
+ }
+ isBlocked = false;
+ final int vPTS = getNextTextureImpl(gl, nextFrame);
+ if( TimeFrameI.INVALID_PTS != vPTS ) {
+ if( null != nextFrame ) {
+ if( STREAM_WORKER_DELAY > 0 ) {
+ Thread.sleep(STREAM_WORKER_DELAY);
+ }
if( !videoFramesDecoded.put(nextFrame) ) {
throw new InternalError("XXX: free "+videoFramesFree+", decoded "+videoFramesDecoded+", "+GLMediaPlayerImpl.this);
}
newFrameAvailable(nextFrame, Platform.currentTimeMillis());
nextFrame = null;
- }
- } catch (InterruptedException e) {
- isBlocked = false;
- if( !shallStop && !shallPause ) {
- streamErr = new StreamException("InterruptedException while decoding: "+GLMediaPlayerImpl.this.toString(), e);
- }
- } catch (Throwable t) {
- streamErr = new StreamException(t.getClass().getSimpleName()+" while decoding: "+GLMediaPlayerImpl.this.toString(), t);
- } finally {
- if( null != nextFrame ) { // put back
- videoFramesFree.put(nextFrame);
- }
- if( null != streamErr ) {
- if( DEBUG ) {
- final Throwable t = null != streamErr.getCause() ? streamErr.getCause() : streamErr;
- System.err.println("Caught StreamException: "+t.getMessage());
- t.printStackTrace();
+ } else {
+ // audio only
+ if( TimeFrameI.END_OF_STREAM_PTS == vPTS ) {
+ // state transition incl. notification
+ shallPause = true;
+ isActive = false;
+ pauseImpl(GLMediaEventListener.EVENT_CHANGE_EOS);
}
- // state transition incl. notification
- shallPause = true;
- isActive = false;
- pause();
}
}
- } else {
- // audio only
- getNextTextureImpl(null, null);
+ } catch (InterruptedException e) {
+ isBlocked = false;
+ if( !shallStop && !shallPause ) {
+ streamErr = new StreamException("InterruptedException while decoding: "+GLMediaPlayerImpl.this.toString(), e);
+ }
+ } catch (Throwable t) {
+ streamErr = new StreamException(t.getClass().getSimpleName()+" while decoding: "+GLMediaPlayerImpl.this.toString(), t);
+ } finally {
+ if( null != nextFrame ) { // put back
+ videoFramesFree.put(nextFrame);
+ }
+ if( null != streamErr ) {
+ if( DEBUG ) {
+ final Throwable t = null != streamErr.getCause() ? streamErr.getCause() : streamErr;
+ System.err.println("Caught StreamException: "+t.getMessage());
+ t.printStackTrace();
+ }
+ // state transition incl. notification
+ shallPause = true;
+ isActive = false;
+ pauseImpl(GLMediaEventListener.EVENT_CHANGE_ERR);
+ }
}
}
}
@@ -1106,13 +1177,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
destroyImpl(gl);
removeAllTextureFrames(gl);
textureCount=0;
- if( null != videoFramesFree ) {
- videoFramesFree.clear();
- }
- if( null != videoFramesDecoded ) {
- videoFramesDecoded.clear();
- }
- changeState(0, State.Uninitialized);
+ changeState(0, State.Uninitialized);
return state;
}
}
@@ -1215,7 +1280,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
final String audioSinkInfo;
final AudioSink audioSink = getAudioSink();
if( null != audioSink ) {
- audioSinkInfo = "AudioSink[frames [d "+audioSink.getEnqueuedFrameCount()+", q "+audioSink.getQueuedFrameCount()+", f "+audioSink.getFreeFrameCount()+"], time "+audioSink.getQueuedTime()+", bytes "+audioSink.getQueuedByteCount()+"]";
+ audioSinkInfo = "AudioSink[frames [p "+audioSink.getEnqueuedFrameCount()+", q "+audioSink.getQueuedFrameCount()+", f "+audioSink.getFreeFrameCount()+", c "+audioSink.getFrameCount()+"], time "+audioSink.getQueuedTime()+", bytes "+audioSink.getQueuedByteCount()+"]";
} else {
audioSinkInfo = "";
}
diff --git a/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java
index 31ac55ec3..9066f3dd1 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java
@@ -83,9 +83,10 @@ public class NullGLMediaPlayer extends GLMediaPlayerImpl {
}
@Override
- protected final boolean getNextTextureImpl(GL gl, TextureFrame nextFrame) {
- nextFrame.setPTS( getAudioPTSImpl() );
- return true;
+ protected final int getNextTextureImpl(GL gl, TextureFrame nextFrame) {
+ final int pts = getAudioPTSImpl();
+ nextFrame.setPTS( pts );
+ return pts;
}
@Override
@@ -102,40 +103,42 @@ public class NullGLMediaPlayer extends GLMediaPlayerImpl {
texData = null;
}
}
-
- @Override
- protected final void initStreamImpl(int vid, int aid) throws IOException {
+
+ public final static TextureData createTestTextureData() {
+ TextureData res = null;
try {
- URLConnection urlConn = IOUtil.getResource("jogl/util/data/av/test-ntsc01-160x90.png", this.getClass().getClassLoader());
+ URLConnection urlConn = IOUtil.getResource("jogl/util/data/av/test-ntsc01-160x90.png", NullGLMediaPlayer.class.getClassLoader());
if(null != urlConn) {
- texData = TextureIO.newTextureData(GLProfile.getGL2ES2(), urlConn.getInputStream(), false, TextureIO.PNG);
+ res = TextureIO.newTextureData(GLProfile.getGL2ES2(), urlConn.getInputStream(), false, TextureIO.PNG);
}
} catch (Exception e) {
e.printStackTrace();
}
- final int _w, _h;
- if(null != texData) {
- _w = texData.getWidth();
- _h = texData.getHeight();
- } else {
- _w = 640;
- _h = 480;
- ByteBuffer buffer = Buffers.newDirectByteBuffer(_w*_h*4);
+ if(null == res) {
+ final int w = 160;
+ final int h = 90;
+ 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();
- texData = new TextureData(GLProfile.getGL2ES2(),
- GL.GL_RGBA, _w, _h, 0,
+ res = new TextureData(GLProfile.getGL2ES2(),
+ GL.GL_RGBA, w, h, 0,
GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false,
false, false, buffer, null);
}
+ return res;
+ }
+
+ @Override
+ protected final void initStreamImpl(int vid, int aid) throws IOException {
+ texData = createTestTextureData();
final int r_aid = GLMediaPlayer.STREAM_ID_NONE == aid ? GLMediaPlayer.STREAM_ID_NONE : GLMediaPlayer.STREAM_ID_AUTO;
final float _fps = 24f;
final int _duration = 10*60*1000; // msec
final int _totalFrames = (int) ( (_duration/1000)*_fps );
updateAttributes(GLMediaPlayer.STREAM_ID_AUTO, r_aid,
- _w, _h, 0,
+ texData.getWidth(), texData.getHeight(), 0,
0, 0, _fps,
_totalFrames, 0, _duration, "png-static", 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 f1213d751..a800f2a31 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java
@@ -40,6 +40,7 @@ import javax.media.opengl.GLException;
import com.jogamp.common.util.VersionNumber;
import com.jogamp.gluegen.runtime.ProcAddressTable;
+import com.jogamp.opengl.util.TimeFrameI;
import com.jogamp.opengl.util.GLPixelStorageModes;
import com.jogamp.opengl.util.av.AudioSink;
import com.jogamp.opengl.util.av.AudioSink.AudioDataFormat;
@@ -169,7 +170,8 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl {
/** Initial audio frame count, ALAudioSink may grow buffer! */
private int initialAudioFrameCount = AV_DEFAULT_AFRAMES;
private final int audioFrameGrowAmount = 8;
- private final int audioFrameLimit = 128;
+ private final int audioFrameLimitWithVideo = 64; // 128;
+ private final int audioFrameLimitAudioOnly = 32; // 64;
private SampleFormat aSampleFmt = null;
private AudioSink.AudioDataFormat avChosenAudioFormat;
private AudioSink.AudioDataFormat sinkChosenAudioFormat;
@@ -235,6 +237,7 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl {
if(null == audioSink) {
throw new GLException("AudioSink null");
}
+ final int audioFrameLimit;
if( null != gl ) {
final GLContextImpl ctx = (GLContextImpl)gl.getContext();
AccessController.doPrivileged(new PrivilegedAction