aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2013-08-14 07:02:59 +0200
committerSven Gothel <[email protected]>2013-08-14 07:02:59 +0200
commitc37629ea8fdcb11f7f8a18e37a4cde57d4ba6a01 (patch)
tree96e6ef2b8db44f3dd331ac78a0a52d5d5ea15e50 /src
parentbc3776633ccad81199a96ff8116195133d862395 (diff)
GLMediaPlayer Multithreaded Decoding: GLMediaPlayer* (Part-3) - WIP
- GLMediaPlayer - Remove State.Stopped and method stop() - redundant, use pause() / destroy() - Add notion of stream IDs - Add API doc: State / Stream-ID incl. html-anchor - Expose video/audio PTS, .. - Expose optional AudioSink - Min multithreaded textureCount is 4 (EGL* and FFMPEG*) - GLMediaPlayerImpl - Move AudioSink rel. impl. to this class, allowing a tight video implementation reusing logic. - Remove 'synchronized' methods, synchronize on State where applicable - implement new methods (see above) - playSpeed is handled partially in AudioSink. If it exeeds AudioSink's capabilities, drop audio and rely solely on video sync. - video sync (WIP) - video pts delay based on geometric weight - reset video SCR if 'out of range', resync w/ PTS - - FramePusher - allow interruption when pausing/stopping, while waiting for next avail free frame to decode. - FFMPEGMediaPlayer - Add proper AudioDataFormat negotiation AudioSink <-> libav - Parse libav's SampleFormat - Remove AudioSink interaction (moved to GLMediaPlayerImpl) - Tests (MovieSimple, MovieCube): - Add aid/vid selection - Add KeyListener for actions: seek(..), play()/pause(), setPlaySpeed(..) - Dump perf-string each 2s - TODO: - Add audio sync in AudioSink, similar to GLMediaPlayer's weighted video delay, here: drop audio frames.
Diffstat (limited to 'src')
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java146
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayerFactory.java5
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/texture/TextureSequence.java5
-rw-r--r--src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java53
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/EGLMediaPlayerImpl.java2
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java740
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java27
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java232
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/impl/OMXGLMediaPlayer.java25
-rw-r--r--src/jogl/native/libav/ffmpeg_tool.h19
-rw-r--r--src/jogl/native/libav/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c223
-rw-r--r--src/test/com/jogamp/opengl/test/android/MovieCubeActivity0.java3
-rw-r--r--src/test/com/jogamp/opengl/test/android/MovieSimpleActivity0.java3
-rw-r--r--src/test/com/jogamp/opengl/test/android/MovieSimpleActivity1.java4
-rw-r--r--src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/TextureSequenceCubeES2.java23
-rw-r--r--src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieCube.java157
-rw-r--r--src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSimple.java228
17 files changed, 1279 insertions, 616 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 a36bce305..fae88ea18 100644
--- a/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java
+++ b/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java
@@ -34,19 +34,37 @@ import javax.media.opengl.GL;
import javax.media.opengl.GLException;
import jogamp.opengl.Debug;
+import jogamp.opengl.util.av.GLMediaPlayerImpl;
import com.jogamp.opengl.util.texture.TextureSequence;
/**
- * Lifecycle of an GLMediaPlayer:
+ * GLMediaPlayer interface specifies a {@link TextureSequence}
+ * with a video stream as it's source.
+ * <p>
+ * Audio maybe supported and played back internally or via an {@link AudioSink} implementation,
+ * if an audio stream is selected in {@link #initGLStream(GL, int, URLConnection, int, int)}.
+ * </p>
+ * <a name="lifecycle"><h5>GLMediaPlayer Lifecycle</h5></a>
+ * <p>
+ * <table border="1">
+ * <tr><th>action</th> <th>state before</th> <th>state after</th></tr>
+ * <tr><td>{@link #initGLStream(GL, int, URLConnection, int, int)}</td> <td>Uninitialized</td> <td>Paused</td></tr>
+ * <tr><td>{@link #play()}</td> <td>Paused</td> <td>Playing</td></tr>
+ * <tr><td>{@link #pause()}</td> <td>Playing</td> <td>Paused</td></tr>
+ * <tr><td>{@link #seek(int)}</td> <td>Playing, Paused</td> <td>Unchanged</td></tr>
+ * <tr><td>{@link #destroy(GL)}</td> <td>ANY</td> <td>Uninitialized</td></tr>
+ * </table>
+ * </p>
+ * <a name="streamIDs"><h5>Audio and video Stream IDs</h5></a>
+ * <p>
* <table border="1">
- * <tr><th>action</th> <th>state before</th> <th>state after</th></tr>
- * <tr><td>{@link #initGLStream(GL, int, URLConnection)}</td> <td>Uninitialized</td> <td>Stopped</td></tr>
- * <tr><td>{@link #start()}</td> <td>Stopped, Paused</td> <td>Playing</td></tr>
- * <tr><td>{@link #stop()}</td> <td>Playing, Paused</td> <td>Stopped</td></tr>
- * <tr><td>{@link #pause()}</td> <td>Playing</td> <td>Paused</td></tr>
- * <tr><td>{@link #destroy(GL)}</td> <td>ANY</td> <td>Uninitialized</td></tr>
+ * <tr><th>value</th> <th>request</th> <th>get</th></tr>
+ * <tr><td>{@link #STREAM_ID_NONE}</td> <td>mute</td> <td>not available</td></tr>
+ * <tr><td>{@link #STREAM_ID_AUTO}</td> <td>auto</td> <td>unspecified</td></tr>
+ * <tr><td>&ge;0</td> <td>specific stream</td> <td>specific stream</td></tr>
* </table>
+ * </p>
* <p>
* Current implementations (check each API doc link for details):
* <ul>
@@ -76,14 +94,21 @@ import com.jogamp.opengl.util.texture.TextureSequence;
*/
public interface GLMediaPlayer extends TextureSequence {
public static final boolean DEBUG = Debug.debug("GLMediaPlayer");
+
+ /** Constant {@value} for <i>mute</i> or <i>not available</i>. See <a href="#streamIDs">Audio and video Stream IDs</a>. */
+ public static final int STREAM_ID_NONE = -2;
+ /** Constant {@value} for <i>auto</i> or <i>unspecified</i>. See <a href="#streamIDs">Audio and video Stream IDs</a>. */
+ public static final int STREAM_ID_AUTO = -1;
public interface GLMediaEventListener extends TexSeqEventListener<GLMediaPlayer> {
- static final int EVENT_CHANGE_SIZE = 1<<0;
- static final int EVENT_CHANGE_FPS = 1<<1;
- static final int EVENT_CHANGE_BPS = 1<<2;
- static final int EVENT_CHANGE_LENGTH = 1<<3;
- static final int EVENT_CHANGE_CODEC = 1<<3;
+ static final int EVENT_CHANGE_VID = 1<<0;
+ static final int EVENT_CHANGE_AID = 1<<1;
+ static final int EVENT_CHANGE_SIZE = 1<<2;
+ static final int EVENT_CHANGE_FPS = 1<<3;
+ static final int EVENT_CHANGE_BPS = 1<<4;
+ static final int EVENT_CHANGE_LENGTH = 1<<5;
+ static final int EVENT_CHANGE_CODEC = 1<<6;
/**
* @param mp the event source
@@ -93,8 +118,11 @@ public interface GLMediaPlayer extends TextureSequence {
public void attributesChanges(GLMediaPlayer mp, int event_mask, long when);
}
+ /**
+ * See <a href="#lifecycle">GLMediaPlayer Lifecycle</a>.
+ */
public enum State {
- Uninitialized(0), Stopped(1), Playing(2), Paused(3);
+ Uninitialized(0), Playing(1), Paused(2);
public final int id;
@@ -120,75 +148,107 @@ public interface GLMediaPlayer extends TextureSequence {
* Sets the stream to be used. Initializes all stream related states inclusive OpenGL ones,
* if <code>gl</code> is not null.
* <p>
- * Uninitialized -> Stopped
+ * <a href="#lifecycle">GLMediaPlayer Lifecycle</a>: Uninitialized -> Paused
* </p>
* @param gl current GL object. If null, no video output and textures will be available.
* @param textureCount desired number of buffered textures to be decoded off-thread, use <code>1</code> for on-thread decoding.
* @param urlConn the stream connection
+ * @param vid video stream id, see <a href="#streamIDs">audio and video Stream IDs</a>
+ * @param aid video stream id, see <a href="#streamIDs">audio and video Stream IDs</a>
* @return the new state
*
* @throws IllegalStateException if not invoked in state Uninitialized
* @throws IOException in case of difficulties to open or process the stream
* @throws GLException in case of difficulties to initialize the GL resources
*/
- public State initGLStream(GL gl, int textureCount, URLConnection urlConn) throws IllegalStateException, GLException, IOException;
+ public State initGLStream(GL gl, int textureCount, URLConnection urlConn, int vid, int aid) throws IllegalStateException, GLException, IOException;
+
+ /**
+ * If implementation uses a {@link AudioSink}, it's instance will be returned.
+ * <p>
+ * The {@link AudioSink} instance is available after {@link #initGLStream(GL, int, URLConnection, int, int)},
+ * if used by implementation.
+ * </p>
+ */
+ public AudioSink getAudioSink();
/**
* Releases the GL and stream resources.
* <p>
- * <code>ANY</code> -> Uninitialized
+ * <a href="#lifecycle">GLMediaPlayer Lifecycle</a>: <code>ANY</code> -> Uninitialized
* </p>
*/
public State destroy(GL gl);
- public void setPlaySpeed(float rate);
+ /**
+ * Sets the playback speed.
+ * <p>
+ * Play speed is set to <i>normal</i>, i.e. <code>1.0f</code>
+ * if <code> abs(1.0f - rate) < 0.01f</code> to simplify test.
+ * </p>
+ * @return true if successful, otherwise false, i.e. due to unsupported value range of implementation.
+ */
+ public boolean setPlaySpeed(float rate);
public float getPlaySpeed();
/**
- * Stopped/Paused -> Playing
+ * <a href="#lifecycle">GLMediaPlayer Lifecycle</a>: Paused -> Playing
*/
- public State start();
+ public State play();
/**
- * Playing -> Paused
+ * <a href="#lifecycle">GLMediaPlayer Lifecycle</a>: Playing -> Paused
*/
public State pause();
/**
- * Playing/Paused -> Stopped
+ * Allowed in state Playing and Paused, otherwise ignored,
+ * see <a href="#lifecycle">GLMediaPlayer Lifecycle</a>.
+ *
+ * @param msec absolute desired time position in milliseconds
+ * @return time current position in milliseconds, after seeking to the desired position
+ **/
+ public int seek(int msec);
+
+ /**
+ * See <a href="#lifecycle">GLMediaPlayer Lifecycle</a>.
+ * @return the current state, either Uninitialized, Playing, Paused
*/
- public State stop();
+ public State getState();
/**
- * @return the current state, either Uninitialized, Stopped, Playing, Paused
+ * Return the video stream id, see <a href="#streamIDs">audio and video Stream IDs</a>.
*/
- public State getState();
+ public int getVID();
/**
- * @return current streaming position in milliseconds
- **/
- public int getCurrentPosition();
-
+ * Return the audio stream id, see <a href="#streamIDs">audio and video Stream IDs</a>.
+ */
+ public int getAID();
+
+ /**
+ * @return the current decoded frame count since {@link #initGLStream(GL, int, URLConnection, int, int)}.
+ */
+ public int getDecodedFrameCount();
+
+ /**
+ * @return the current presented frame count since {@link #initGLStream(GL, int, URLConnection, int, int)},
+ * increased by {@link #getNextTexture(GL, boolean)}.
+ */
+ public int getPresentedFrameCount();
+
/**
- * @return current video PTS in milliseconds of {@link #getLastTexture()}
+ * @return current video presentation timestamp (PTS) in milliseconds of {@link #getLastTexture()}
**/
public int getVideoPTS();
/**
- * @return current audio PTS in milliseconds.
+ * @return current audio presentation timestamp (PTS) in milliseconds.
**/
public int getAudioPTS();
/**
- * Allowed in state Stopped, Playing and Paused, otherwise ignored.
- *
- * @param msec absolute desired time position in milliseconds
- * @return time current position in milliseconds, after seeking to the desired position
- **/
- public int seek(int msec);
-
- /**
* {@inheritDoc}
*/
@Override
@@ -225,7 +285,13 @@ public interface GLMediaPlayer extends TextureSequence {
* <i>Warning:</i> Optional information, may not be supported by implementation.
* @return the total number of video frames
*/
- public long getTotalFrames();
+ public int getVideoFrames();
+
+ /**
+ * <i>Warning:</i> Optional information, may not be supported by implementation.
+ * @return the total number of audio frames
+ */
+ public int getAudioFrames();
/**
* @return total duration of stream in msec.
@@ -262,6 +328,8 @@ public interface GLMediaPlayer extends TextureSequence {
public String toString();
+ public String getPerfString();
+
public void addEventListener(GLMediaEventListener l);
public void removeEventListener(GLMediaEventListener l);
diff --git a/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayerFactory.java b/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayerFactory.java
index 2707dd6d2..c7e1ab5e6 100644
--- a/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayerFactory.java
+++ b/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayerFactory.java
@@ -47,10 +47,13 @@ public class GLMediaPlayerFactory {
sink = create(cl, FFMPEGMediaPlayerClazzName);
}
if( null == sink ) {
- sink = new NullGLMediaPlayer();
+ sink = createNull();
}
return sink;
}
+ public static GLMediaPlayer createNull() {
+ return new NullGLMediaPlayer();
+ }
public static GLMediaPlayer create(final ClassLoader cl, String implName) {
try {
diff --git a/src/jogl/classes/com/jogamp/opengl/util/texture/TextureSequence.java b/src/jogl/classes/com/jogamp/opengl/util/texture/TextureSequence.java
index 3f739b2cc..50801e791 100644
--- a/src/jogl/classes/com/jogamp/opengl/util/texture/TextureSequence.java
+++ b/src/jogl/classes/com/jogamp/opengl/util/texture/TextureSequence.java
@@ -110,9 +110,12 @@ public interface TextureSequence {
* to associated related data.
*/
public static class TextureFrame {
+ /** Constant marking an invalid PTS, i.e. Integer.MIN_VALUE {@value}. */
+ public static final int INVALID_PTS = Integer.MIN_VALUE;
+
public TextureFrame(Texture t) {
texture = t;
- pts = 0;
+ pts = INVALID_PTS;
}
public final Texture getTexture() { return texture; }
diff --git a/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java b/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java
index 765cda084..e14642c34 100644
--- a/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java
+++ b/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java
@@ -28,13 +28,13 @@
package jogamp.opengl.android.av;
import java.io.IOException;
-import java.nio.Buffer;
import javax.media.opengl.GL;
import javax.media.opengl.GLES2;
import com.jogamp.common.os.AndroidVersion;
import com.jogamp.common.os.Platform;
+import com.jogamp.opengl.util.av.GLMediaPlayer;
import com.jogamp.opengl.util.texture.Texture;
import com.jogamp.opengl.util.texture.TextureSequence;
@@ -100,7 +100,7 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl {
}
@Override
- protected final boolean startImpl() {
+ protected final boolean playImpl() {
if(null != mp) {
try {
mp.start();
@@ -131,22 +131,6 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl {
}
@Override
- protected final boolean stopImpl() {
- if(null != mp) {
- wakeUp(false);
- try {
- mp.stop();
- return true;
- } catch (IllegalStateException ise) {
- if(DEBUG) {
- ise.printStackTrace();
- }
- }
- }
- return false;
- }
-
- @Override
protected final int seekImpl(int msec) {
if(null != mp) {
mp.seekTo(msec);
@@ -165,15 +149,19 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl {
}
@Override
- protected final int getCurrentPositionImpl() { return null != mp ? mp.getCurrentPosition() : 0; }
-
- @Override
- protected final int getAudioPTSImpl() { return getCurrentPositionImpl(); }
+ protected final int getAudioPTSImpl() { return null != mp ? mp.getCurrentPosition() : 0; }
@Override
protected final void destroyImpl(GL gl) {
if(null != mp) {
wakeUp(false);
+ try {
+ mp.stop();
+ } catch (IllegalStateException ise) {
+ if(DEBUG) {
+ ise.printStackTrace();
+ }
+ }
mp.release();
mp = null;
}
@@ -198,8 +186,13 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl {
}
@Override
- protected final void initGLStreamImpl(GL gl) throws IOException {
+ protected final void initGLStreamImpl(GL gl, int vid, int aid) throws IOException {
if(null!=mp && null!=urlConn) {
+ if( GLMediaPlayer.STREAM_ID_NONE == aid ) {
+ mp.setVolume(0f, 0f);
+ // FIXME: Disable audio handling
+ } // else FIXME: Select aid !
+ // Note: Both FIXMEs seem to be n/a via Android's MediaPlayer -> Switch to API level 16 MediaCodec/MediaExtractor ..
try {
final Uri uri = Uri.parse(urlConn.getURL().toExternalForm());
mp.setDataSource(StaticContext.getContext(), uri);
@@ -213,20 +206,18 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl {
if( null == stex ) {
throw new InternalError("XXX");
}
- final Surface surf = new Surface(stex);
- mp.setSurface(surf);
- surf.release();
mp.setSurface(null);
try {
mp.prepare();
} catch (IOException ioe) {
throw new IOException("MediaPlayer failed to process stream <"+urlConn.getURL().toExternalForm()+">: "+ioe.getMessage(), ioe);
}
+ final int r_aid = GLMediaPlayer.STREAM_ID_NONE == aid ? GLMediaPlayer.STREAM_ID_NONE : GLMediaPlayer.STREAM_ID_AUTO;
final String icodec = "android";
- updateAttributes(mp.getVideoWidth(), mp.getVideoHeight(),
- 0, 0, 0,
- 0f, 0, mp.getDuration(),
- icodec, icodec);
+ updateAttributes(GLMediaPlayer.STREAM_ID_AUTO, r_aid,
+ mp.getVideoWidth(), mp.getVideoHeight(), 0,
+ 0, 0, 0f,
+ 0, 0, mp.getDuration(), icodec, icodec);
}
}
@@ -264,8 +255,6 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl {
}
return true;
}
- @Override
- protected final void syncFrame2Audio(TextureFrame frame) {}
@Override
protected final TextureSequence.TextureFrame createTexImage(GL gl, int texName) {
diff --git a/src/jogl/classes/jogamp/opengl/util/av/EGLMediaPlayerImpl.java b/src/jogl/classes/jogamp/opengl/util/av/EGLMediaPlayerImpl.java
index 57d5ff625..db2146cdc 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/EGLMediaPlayerImpl.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/EGLMediaPlayerImpl.java
@@ -84,7 +84,7 @@ public abstract class EGLMediaPlayerImpl extends GLMediaPlayerImpl {
}
@Override
protected final int validateTextureCount(int desiredTextureCount) {
- return desiredTextureCount>1 ? desiredTextureCount : 2;
+ return desiredTextureCount>2 ? Math.max(4, desiredTextureCount) : 2;
}
@Override
diff --git a/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java b/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java
index bc297dc21..c1cfc0d95 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java
@@ -42,6 +42,7 @@ import javax.media.opengl.GLES2;
import javax.media.opengl.GLException;
import javax.media.opengl.GLProfile;
+import com.jogamp.opengl.util.av.AudioSink;
import com.jogamp.opengl.util.av.GLMediaPlayer;
import com.jogamp.opengl.util.texture.Texture;
import com.jogamp.opengl.util.texture.TextureSequence;
@@ -62,7 +63,11 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
protected static final String unknown = "unknown";
- protected State state;
+ /** Default texture count w/o threading, value {@value}. */
+ protected static final int TEXTURE_COUNT_DEFAULT = 2;
+
+ protected volatile State state;
+ private Object stateLock = new Object();
protected int textureCount;
protected int textureTarget;
@@ -79,30 +84,72 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
protected volatile float playSpeed = 1.0f;
- /** Shall be set by the {@link #initGLStreamImpl(GL)} method implementation. */
+ /** Shall be set by the {@link #initGLStreamImpl(GL, int, int)} method implementation. */
+ protected int vid = GLMediaPlayer.STREAM_ID_AUTO;
+ /** Shall be set by the {@link #initGLStreamImpl(GL, int, int)} method implementation. */
+ protected int aid = GLMediaPlayer.STREAM_ID_AUTO;
+ /** Shall be set by the {@link #initGLStreamImpl(GL, int, int)} method implementation. */
protected int width = 0;
- /** Shall be set by the {@link #initGLStreamImpl(GL)} method implementation. */
+ /** Shall be set by the {@link #initGLStreamImpl(GL, int, int)} method implementation. */
protected int height = 0;
- /** Video fps. Shall be set by the {@link #initGLStreamImpl(GL)} method implementation. */
+ /** Video fps. Shall be set by the {@link #initGLStreamImpl(GL, int, int)} method implementation. */
protected float fps = 0;
- /** Stream bps. Shall be set by the {@link #initGLStreamImpl(GL)} method implementation. */
+ protected int frame_period = 0;
+ /** Stream bps. Shall be set by the {@link #initGLStreamImpl(GL, int, int)} method implementation. */
protected int bps_stream = 0;
- /** Video bps. Shall be set by the {@link #initGLStreamImpl(GL)} method implementation. */
+ /** Video bps. Shall be set by the {@link #initGLStreamImpl(GL, int, int)} method implementation. */
protected int bps_video = 0;
- /** Audio bps. Shall be set by the {@link #initGLStreamImpl(GL)} method implementation. */
+ /** Audio bps. Shall be set by the {@link #initGLStreamImpl(GL, int, int)} method implementation. */
protected int bps_audio = 0;
- /** In frames. Shall be set by the {@link #initGLStreamImpl(GL)} method implementation. */
- protected int totalFrames = 0;
- /** In ms. Shall be set by the {@link #initGLStreamImpl(GL)} method implementation. */
+ /** In frames. Shall be set by the {@link #initGLStreamImpl(GL, int, int)} method implementation. */
+ protected int videoFrames = 0;
+ /** In frames. Shall be set by the {@link #initGLStreamImpl(GL, int, int)} method implementation. */
+ protected int audioFrames = 0;
+ /** In ms. Shall be set by the {@link #initGLStreamImpl(GL, int, int)} method implementation. */
protected int duration = 0;
- /** Shall be set by the {@link #initGLStreamImpl(GL)} method implementation. */
+ /** Shall be set by the {@link #initGLStreamImpl(GL, int, int)} method implementation. */
protected String acodec = unknown;
- /** Shall be set by the {@link #initGLStreamImpl(GL)} method implementation. */
+ /** Shall be set by the {@link #initGLStreamImpl(GL, int, int)} method implementation. */
protected String vcodec = unknown;
- protected int frameNumber = 0;
- protected int currentVideoPTS = 0;
+ protected volatile int decodedFrameCount = 0;
+ protected int presentedFrameCount = 0;
+ protected volatile int video_pts_last = 0;
+
+ /** See {@link #getAudioSink()}. Set by implementation if used from within {@link #initGLStreamImpl(GL, int, int)}! */
+ protected AudioSink audioSink = null;
+ protected boolean audioSinkPlaySpeedSet = false;
+
+ /** System Clock Reference (SCR) of first audio PTS at start time. */
+ private long audio_scr_t0 = 0;
+ private boolean audioSCR_reset = true;
+ /** System Clock Reference (SCR) of first video frame at start time. */
+ private long video_scr_t0 = 0;
+ /** System Clock Reference (SCR) PTS offset, i.e. first video PTS at start time. */
+ private int video_scr_pts = 0;
+ /** Cumulative video pts diff. */
+ private float video_dpts_cum = 0;
+ /** Cumulative video frames. */
+ private int video_dpts_count = 0;
+ /** Number of min frame count required for video cumulative sync. */
+ private static final int VIDEO_DPTS_NUM = 20;
+ /** Cumulative coefficient, value {@value}. */
+ private static final float VIDEO_DPTS_COEFF = 0.7943282f; // (float) Math.exp(Math.log(0.01) / VIDEO_DPTS_NUM);
+ /** Maximum valid video pts diff. */
+ private static final int VIDEO_DPTS_MAX = 5000; // 5s max diff
+ /** Trigger video PTS reset with given cause as bitfield. */
+ private volatile int videoSCR_reset = 0;
+
+ private final boolean isSCRCause(int bit) { return 0 != ( bit & videoSCR_reset); }
+ /** SCR reset due to: Start, Resume, Seek, .. */
+ private static final int SCR_RESET_FORCE = 1 << 0;
+ /** SCR reset due to: PlaySpeed */
+ private static final int SCR_RESET_SPEED = 1 << 1;
+
+ /** Latched video PTS reset, to wait until valid pts after invalidation of cached ones. Currently [1..{@link #VIDEO_DPTS_NUM}] frames. */
+ private int videoSCR_reset_latch = 0;
+
protected SyncedRingbuffer<TextureFrame> videoFramesFree = null;
protected SyncedRingbuffer<TextureFrame> videoFramesDecoded = null;
protected volatile TextureFrame lastFrame = null;
@@ -201,144 +248,205 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
}
@Override
- public final float getPlaySpeed() {
- return playSpeed;
- }
+ public final int getDecodedFrameCount() { return decodedFrameCount; }
+
+ @Override
+ public final int getPresentedFrameCount() { return this.presentedFrameCount; }
+
+ @Override
+ public final int getVideoPTS() { return video_pts_last; }
@Override
- public final synchronized void setPlaySpeed(float rate) {
- if(State.Uninitialized != state && setPlaySpeedImpl(rate)) {
- playSpeed = rate;
+ public final int getAudioPTS() {
+ if( State.Uninitialized != state ) {
+ return getAudioPTSImpl();
}
- if(DEBUG) { System.err.println("SetPlaySpeed: "+toString()); }
+ return 0;
}
- protected abstract boolean setPlaySpeedImpl(float rate);
-
- public final State start() {
- switch( state ) {
- case Stopped:
- /** fall-through intended */
- case Paused:
- if( startImpl() ) {
- resumeFramePusher();
- state = State.Playing;
- }
- default:
+ /** Override if not using audioSink! */
+ protected int getAudioPTSImpl() {
+ if( null != audioSink ) {
+ return audioSink.getPTS();
+ } else {
+ return 0;
}
- if(DEBUG) { System.err.println("Start: "+toString()); }
- return state;
}
- protected abstract boolean startImpl();
- public final State pause() {
- if( State.Playing == state && pauseImpl() ) {
- pauseFramePusher();
- state = State.Paused;
+ public final State getState() { return state; }
+
+ public final State play() {
+ synchronized( stateLock ) {
+ switch( state ) {
+ case Paused:
+ if( playImpl() ) {
+ resetAudioVideoSCR(SCR_RESET_FORCE);
+ resumeFramePusher();
+ if( null != audioSink ) {
+ audioSink.play();
+ }
+ state = State.Playing;
+ }
+ default:
+ }
+ if(DEBUG) { System.err.println("Start: "+toString()); }
+ return state;
}
- if(DEBUG) { System.err.println("Pause: "+toString()); }
- return state;
}
- protected abstract boolean pauseImpl();
+ protected abstract boolean playImpl();
- public final State stop() {
- switch( state ) {
- case Playing:
- /** fall-through intended */
- case Paused:
- if( stopImpl() ) {
+ public final State pause() {
+ synchronized( stateLock ) {
+ if( State.Playing == state ) {
+ State _state = state;
+ state = State.Paused;
+ if( pauseImpl() ) {
+ _state = State.Paused;
pauseFramePusher();
- state = State.Stopped;
+ if( null != audioSink ) {
+ audioSink.pause();
+ }
}
- default:
+ state = _state;
+ }
+ if(DEBUG) { System.err.println("Pause: "+toString()); }
+ return state;
}
- if(DEBUG) { System.err.println("Stop: "+toString()); }
- return state;
}
- protected abstract boolean stopImpl();
+ protected abstract boolean pauseImpl();
- @Override
- public final int getCurrentPosition() {
- if( State.Uninitialized != state ) {
- return getCurrentPositionImpl();
+ public final int seek(int msec) {
+ synchronized( stateLock ) {
+ final int pts1;
+ switch(state) {
+ case Playing:
+ case Paused:
+ final State _state = state;
+ state = State.Paused;
+ pauseFramePusher();
+ resetAudioVideoSCR(SCR_RESET_FORCE);
+ pts1 = seekImpl(msec);
+ if( null != audioSink ) {
+ audioSink.flush();
+ if( State.Playing == _state ) {
+ audioSink.play(); // cont. w/ new data
+ }
+ }
+ resumeFramePusher();
+ state = _state;
+ break;
+ default:
+ pts1 = 0;
+ }
+ if(DEBUG) { System.err.println("Seek("+msec+"): "+toString()); }
+ return pts1;
}
- return 0;
}
- protected abstract int getCurrentPositionImpl();
+ protected abstract int seekImpl(int msec);
@Override
- public final int getVideoPTS() { return currentVideoPTS; }
+ public final float getPlaySpeed() {
+ return playSpeed;
+ }
@Override
- public final int getAudioPTS() {
- if( State.Uninitialized != state ) {
- return getAudioPTSImpl();
+ public final boolean setPlaySpeed(float rate) {
+ synchronized( stateLock ) {
+ boolean res = false;
+ if(State.Uninitialized != state ) {
+ if( rate > 0.01f ) {
+ if( Math.abs(1.0f - rate) < 0.01f ) {
+ rate = 1.0f;
+ }
+ if( setPlaySpeedImpl(rate) ) {
+ resetAudioVideoSCR(SCR_RESET_SPEED);
+ playSpeed = rate;
+ if(DEBUG) { System.err.println("SetPlaySpeed: "+toString()); }
+ res = true;
+ }
+ }
+ }
+ return res;
}
- return 0;
- }
- protected abstract int getAudioPTSImpl();
-
- public final int seek(int msec) {
- final int pts1;
- switch(state) {
- case Stopped:
- case Playing:
- case Paused:
- pauseFramePusher();
- pts1 = seekImpl(msec);
- currentVideoPTS=pts1;
- resumeFramePusher();
- break;
- default:
- pts1 = 0;
+ }
+ /**
+ * Override if not using AudioSink, or AudioSink's {@link AudioSink#setPlaySpeed(float)} is not sufficient!
+ * <p>
+ * AudioSink shall respect <code>!audioSinkPlaySpeedSet</code> to determine data_size
+ * at {@link AudioSink#enqueueData(com.jogamp.opengl.util.av.AudioSink.AudioFrame)}.
+ * </p>
+ */
+ protected boolean setPlaySpeedImpl(float rate) {
+ if( null != audioSink ) {
+ audioSinkPlaySpeedSet = audioSink.setPlaySpeed(rate);
}
- if(DEBUG) { System.err.println("Seek("+msec+"): "+toString()); }
- return pts1;
+ // still true, even if audioSink rejects command since we deal w/ video sync
+ // and AudioSink w/ audioSinkPlaySpeedSet at enqueueData(..).
+ return true;
}
- protected abstract int seekImpl(int msec);
-
- public final State getState() { return state; }
-
+
@Override
- public final State initGLStream(GL gl, int reqTextureCount, URLConnection urlConn) throws IllegalStateException, GLException, IOException {
- if(State.Uninitialized != state) {
- throw new IllegalStateException("Instance not in state "+State.Uninitialized+", but "+state+", "+this);
- }
- this.urlConn = urlConn;
- if (this.urlConn != null) {
- try {
- if( null != gl ) {
- removeAllTextureFrames(gl);
- textureCount = validateTextureCount(reqTextureCount);
- if( textureCount < 2 ) {
- throw new InternalError("Validated texture count < 2: "+textureCount);
- }
- initGLStreamImpl(gl); // also initializes width, height, .. etc
- videoFramesFree = new SyncedRingbuffer<TextureFrame>(createTexFrames(gl, textureCount), true /* full */);
- if( 2 < textureCount ) {
- videoFramesDecoded = new SyncedRingbuffer<TextureFrame>(new TextureFrame[textureCount], false /* full */);
- framePusher = new FramePusher(gl, requiresOffthreadGLCtx());
- framePusher.doStart();
- } else {
- videoFramesDecoded = null;
+ public final State initGLStream(GL gl, int reqTextureCount, URLConnection urlConn, int vid, int aid) throws IllegalStateException, GLException, IOException {
+ synchronized( stateLock ) {
+ if(State.Uninitialized != state) {
+ throw new IllegalStateException("Instance not in state "+State.Uninitialized+", but "+state+", "+this);
+ }
+ decodedFrameCount = 0;
+ presentedFrameCount = 0;
+ this.urlConn = urlConn;
+ if (this.urlConn != null) {
+ try {
+ if( null != gl ) {
+ removeAllTextureFrames(gl);
+ textureCount = validateTextureCount(reqTextureCount);
+ if( textureCount < TEXTURE_COUNT_DEFAULT ) {
+ throw new InternalError("Validated texture count < "+TEXTURE_COUNT_DEFAULT+": "+textureCount);
+ }
+ initGLStreamImpl(gl, vid, aid); // also initializes width, height, .. etc
+ videoFramesFree = new SyncedRingbuffer<TextureFrame>(createTexFrames(gl, textureCount), true /* full */);
+ if( TEXTURE_COUNT_DEFAULT < textureCount ) {
+ videoFramesDecoded = new SyncedRingbuffer<TextureFrame>(new TextureFrame[textureCount], false /* full */);
+ framePusher = new FramePusher(gl, requiresOffthreadGLCtx());
+ framePusher.doStart();
+ } else {
+ videoFramesDecoded = null;
+ }
+ lastFrame = videoFramesFree.getBlocking(false /* clearRef */ );
+ state = State.Paused;
}
- lastFrame = videoFramesFree.getBlocking(false /* clearRef */ );
+ return state;
+ } catch (Throwable t) {
+ throw new GLException("Error initializing GL resources", t);
}
- state = State.Stopped;
- return state;
- } catch (Throwable t) {
- throw new GLException("Error initializing GL resources", t);
}
+ return state;
}
- return state;
}
+ /**
+ * Implementation shall set the following set of data here
+ * @see #vid
+ * @see #aid
+ * @see #width
+ * @see #height
+ * @see #fps
+ * @see #bps_stream
+ * @see #videoFrames
+ * @see #audioFrames
+ * @see #acodec
+ * @see #vcodec
+ */
+ protected abstract void initGLStreamImpl(GL gl, int vid, int aid) throws IOException;
+
/**
* Returns the validated number of textures to be handled.
* <p>
- * Default is always 2 textures, last texture and the decoding texture.
+ * Default is 2 textures w/o threading, last texture and the decoding texture.
+ * </p>
+ * <p>
+ * &gt; 2 textures is used for threaded decoding, a minimum of 4 textures seems reasonable in this case.
* </p>
*/
protected int validateTextureCount(int desiredTextureCount) {
- return 2;
+ return TEXTURE_COUNT_DEFAULT;
}
protected boolean requiresOffthreadGLCtx() { return false; }
@@ -405,6 +513,18 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
mustFlipVertically);
}
+ protected void destroyTexFrame(GL gl, TextureFrame frame) {
+ frame.getTexture().destroy(gl);
+ }
+
+ @Override
+ public final TextureFrame getLastTexture() throws IllegalStateException {
+ if(State.Uninitialized == state) {
+ throw new IllegalStateException("Instance not initialized: "+this);
+ }
+ return lastFrame;
+ }
+
private final void removeAllTextureFrames(GL gl) {
if( null != videoFramesFree ) {
final TextureFrame[] texFrames = videoFramesFree.getArray();
@@ -417,79 +537,209 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
destroyTexFrame(gl, frame);
texFrames[i] = null;
}
+ System.err.println(Thread.currentThread().getName()+"> Clear TexFrame["+i+"]: "+frame+" -> null");
}
}
textureCount=0;
}
- protected void destroyTexFrame(GL gl, TextureFrame frame) {
- frame.getTexture().destroy(gl);
- }
-
- /**
- * Implementation shall set the following set of data here
- * @param gl TODO
- * @see #width
- * @see #height
- * @see #fps
- * @see #bps_stream
- * @see #totalFrames
- * @see #acodec
- * @see #vcodec
- */
- protected abstract void initGLStreamImpl(GL gl) throws IOException;
@Override
- public final TextureFrame getLastTexture() throws IllegalStateException {
- if(State.Uninitialized == state) {
- throw new IllegalStateException("Instance not initialized: "+this);
+ public final TextureFrame getNextTexture(GL gl, boolean blocking) throws IllegalStateException {
+ synchronized( stateLock ) {
+ if(State.Uninitialized == state) {
+ throw new IllegalStateException("Instance not initialized: "+this);
+ }
+ if(State.Playing == state) {
+ TextureFrame nextFrame = null;
+ boolean ok = true;
+ boolean dropFrame = false;
+ try {
+ do {
+ if( TEXTURE_COUNT_DEFAULT < textureCount ) {
+ nextFrame = videoFramesDecoded.getBlocking(false /* clearRef */ );
+ } else {
+ nextFrame = videoFramesFree.getBlocking(false /* clearRef */ );
+ if( getNextTextureImpl(gl, nextFrame, blocking) ) {
+ newFrameAvailable(nextFrame);
+ } else {
+ ok = false;
+ }
+ }
+ if( ok ) {
+ presentedFrameCount++;
+ final int video_pts;
+ if( 0 != videoSCR_reset ) {
+ if( isSCRCause(SCR_RESET_FORCE) ) {
+ videoSCR_reset_latch = VIDEO_DPTS_NUM / 2;
+ resetVideoDPTS();
+ resetAllVideoPTS();
+ } else {
+ // SCR_RESET_SPEED
+ videoSCR_reset_latch = 1;
+ }
+ videoSCR_reset = 0;
+ video_pts = TextureFrame.INVALID_PTS;
+ } else {
+ video_pts = nextFrame.getPTS();
+ }
+ if( video_pts != TextureFrame.INVALID_PTS ) {
+ final int frame_period_last = video_pts - video_pts_last; // rendering loop interrupted ?
+ if( videoSCR_reset_latch > 0 || frame_period_last > frame_period*10 ) {
+ if( videoSCR_reset_latch > 0 ) {
+ videoSCR_reset_latch--;
+ }
+ setFirstVideoPTS2SCR( video_pts );
+ }
+ final int scr_pts = video_scr_pts +
+ (int) ( ( System.currentTimeMillis() - video_scr_t0 ) * playSpeed );
+ final int d_vpts = video_pts - scr_pts;
+ if( -VIDEO_DPTS_MAX > d_vpts || d_vpts > VIDEO_DPTS_MAX ) {
+ if( DEBUG ) {
+ System.err.println( getPerfStringImpl( scr_pts, video_pts, d_vpts, 0 ) );
+ }
+ } else {
+ video_dpts_count++;
+ video_dpts_cum = d_vpts + VIDEO_DPTS_COEFF * video_dpts_cum;
+ final int video_dpts_avg_diff = getVideoDPTSAvg();
+ if( DEBUG ) {
+ System.err.println( getPerfStringImpl( scr_pts, video_pts, d_vpts, video_dpts_avg_diff ) );
+ }
+ if( blocking && syncAVRequired() ) {
+ if( !syncAV( (int) ( video_dpts_avg_diff / playSpeed + 0.5f ) ) ) {
+ resetVideoDPTS();
+ dropFrame = true;
+ }
+ }
+ video_pts_last = video_pts;
+ }
+ }
+ final TextureFrame _lastFrame = lastFrame;
+ lastFrame = nextFrame;
+ videoFramesFree.putBlocking(_lastFrame);
+ }
+ } while( dropFrame );
+ } catch (InterruptedException e) {
+ ok = false;
+ e.printStackTrace();
+ } finally {
+ if( !ok && null != nextFrame ) { // put back
+ videoFramesFree.put(nextFrame);
+ }
+ }
+ }
+ return lastFrame;
}
- return lastFrame;
}
+ protected abstract boolean getNextTextureImpl(GL gl, TextureFrame nextFrame, boolean blocking);
+ protected boolean syncAVRequired() { return false; }
+ /**
+ * {@inheritDoc}
+ * <p>
+ * Note: All {@link AudioSink} operations are performed from {@link GLMediaPlayerImpl},
+ * i.e. {@link #play()}, {@link #pause()}, {@link #seek(int)}, {@link #setPlaySpeed(float)}, {@link #getAudioPTS()}.
+ * </p>
+ * <p>
+ * Implementations using an {@link AudioSink} shall write it's instance to {@link #audioSink}
+ * from within their {@link #initGLStreamImpl(GL, int, int)} implementation.
+ * </p>
+ */
@Override
- public final synchronized TextureFrame getNextTexture(GL gl, boolean blocking) throws IllegalStateException {
- if(State.Uninitialized == state) {
- throw new IllegalStateException("Instance not initialized: "+this);
+ public final AudioSink getAudioSink() { return audioSink; }
+
+ /**
+ * To be called from implementation at 1st PTS after start
+ * w/ current pts value in milliseconds.
+ * @param audio_scr_t0
+ */
+ protected void setFirstAudioPTS2SCR(int pts) {
+ if( audioSCR_reset ) {
+ audio_scr_t0 = System.currentTimeMillis() - pts;
+ audioSCR_reset = false;
}
- if(State.Playing == state) {
- TextureFrame nextFrame = null;
- boolean ok = true;
- try {
- if( 2 < textureCount ) {
- nextFrame = videoFramesDecoded.getBlocking(false /* clearRef */ );
- } else {
- nextFrame = videoFramesFree.getBlocking(false /* clearRef */ );
- if( getNextTextureImpl(gl, nextFrame, blocking) ) {
- newFrameAvailable(nextFrame);
- } else {
- ok = false;
- }
- }
- if( ok ) {
- currentVideoPTS = nextFrame.getPTS();
- if( blocking ) {
- syncFrame2Audio(nextFrame);
- }
- final TextureFrame _lastFrame = lastFrame;
- lastFrame = nextFrame;
- videoFramesFree.putBlocking(_lastFrame);
- }
- } catch (InterruptedException e) {
- ok = false;
- e.printStackTrace();
- } finally {
- if( !ok && null != nextFrame ) { // put back
- videoFramesFree.put(nextFrame);
- }
+ }
+ private void setFirstVideoPTS2SCR(int pts) {
+ // video_scr_t0 = System.currentTimeMillis() - pts;
+ video_scr_t0 = System.currentTimeMillis();
+ video_scr_pts = pts;
+ }
+ private void resetAllVideoPTS() {
+ if( null != videoFramesFree ) {
+ final TextureFrame[] texFrames = videoFramesFree.getArray();
+ for(int i=0; i<texFrames.length; i++) {
+ final TextureFrame frame = texFrames[i];
+ frame.setPTS(TextureFrame.INVALID_PTS);
+ }
+ }
+ }
+ private void resetVideoDPTS() {
+ video_dpts_cum = 0;
+ video_dpts_count = 0;
+ }
+ private final int getVideoDPTSAvg() {
+ if( video_dpts_count < VIDEO_DPTS_NUM ) {
+ return 0;
+ } else {
+ return (int) ( video_dpts_cum * (1.0f - VIDEO_DPTS_COEFF) + 0.5f );
+ }
+ }
+
+ private void resetAudioVideoSCR(int cause) {
+ audioSCR_reset = true;
+ videoSCR_reset |= cause;
+ }
+
+ /**
+ * Synchronizes A-V.
+ * <p>
+ * https://en.wikipedia.org/wiki/Audio_to_video_synchronization
+ * <pre>
+ * d_av = v_pts - a_pts;
+ * </pre>
+ * </p>
+ * <p>
+ * Recommendation of audio/video pts time lead/lag at production:
+ * <ul>
+ * <li>Overall: +40ms and -60ms audio ahead video / audio after video</li>
+ * <li>Each stage: +5ms and -15ms. audio ahead video / audio after video</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Recommendation of av pts time lead/lag at presentation:
+ * <ul>
+ * <li>TV: +15ms and -45ms. audio ahead video / audio after video.</li>
+ * <li>Film: +22ms and -22ms. audio ahead video / audio after video.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Maybe implemented as follows:
+ * <pre>
+ * d_av = vpts - apts;
+ * d_av < -22: audio after video == video ahead audio -> drop
+ * d_av > 22: audio ahead video == video after audio -> sleep(d_av - 10)
+ * </pre>
+ * </p>
+ * <p>
+ * Returns true if audio is ahead of video, otherwise false (video is ahead of audio).
+ * In case of the latter (false), the video frame shall be dropped!
+ * </p>
+ * @param frame
+ * @return true if audio is ahead of video, otherwise false (video is ahead of audio)
+ */
+ protected boolean syncAV(int d_vpts) {
+ if( d_vpts > 22 ) {
+ if( DEBUG ) {
+ System.err.println("V (sleep): "+(d_vpts - 22 / 2)+" ms");
}
+ try {
+ Thread.sleep( d_vpts - 22 / 2 );
+ } catch (InterruptedException e) { }
}
- return lastFrame;
+ return true;
}
- protected abstract boolean getNextTextureImpl(GL gl, TextureFrame nextFrame, boolean blocking);
- protected abstract void syncFrame2Audio(TextureFrame frame);
private final void newFrameAvailable(TextureFrame frame) {
- frameNumber++;
+ decodedFrameCount++;
synchronized(eventListenersLock) {
for(Iterator<GLMediaEventListener> i = eventListeners.iterator(); i.hasNext(); ) {
i.next().newFrameAvailable(this, frame, System.currentTimeMillis());
@@ -500,6 +750,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
class FramePusher extends Thread {
private volatile boolean isRunning = false;
private volatile boolean isActive = false;
+ private volatile boolean isBlocked = false;
private volatile boolean shallPause = true;
private volatile boolean shallStop = false;
@@ -560,6 +811,9 @@ 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
@@ -595,6 +849,9 @@ 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 {
@@ -629,7 +886,9 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
try {
this.wait(); // wait until resumed
} catch (InterruptedException e) {
- e.printStackTrace();
+ if( !shallPause ) {
+ e.printStackTrace();
+ }
}
}
isActive = true;
@@ -639,23 +898,30 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
if( !shallStop ) {
TextureFrame nextFrame = null;
- boolean ok = false;
try {
- nextFrame = videoFramesFree.getBlocking(true /* clearRef */ );
+ isBlocked = true;
+ nextFrame = videoFramesFree.getBlocking(false /* clearRef */ );
+ isBlocked = false;
+ nextFrame.setPTS( TextureFrame.INVALID_PTS ); // mark invalid until processed!
if( getNextTextureImpl(gl, nextFrame, true) ) {
- gl.glFinish();
- videoFramesDecoded.putBlocking(nextFrame);
- newFrameAvailable(nextFrame);
- ok = true;
+ // gl.glFinish();
+ gl.glFlush(); // even better: sync object!
+ if( !videoFramesDecoded.put(nextFrame) ) {
+ throw new InternalError("XXX: "+GLMediaPlayerImpl.this);
+ }
+ final TextureFrame _nextFrame = nextFrame;
+ nextFrame = null;
+ newFrameAvailable(_nextFrame);
}
} catch (InterruptedException e) {
+ isBlocked = false;
if( !shallStop && !shallPause ) {
e.printStackTrace(); // oops
shallPause = false;
shallStop = true;
}
} finally {
- if( !ok && null != nextFrame ) { // put back
+ if( null != nextFrame ) { // put back
videoFramesFree.put(nextFrame);
}
}
@@ -689,10 +955,18 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
}
}
- protected final void updateAttributes(int width, int height, int bps_stream, int bps_video, int bps_audio,
- float fps, int totalFrames, int duration,
- String vcodec, String acodec) {
+ protected final void updateAttributes(int vid, int aid, int width, int height, int bps_stream,
+ int bps_video, int bps_audio, float fps,
+ int videoFrames, int audioFrames, int duration, String vcodec, String acodec) {
int event_mask = 0;
+ if( this.vid != vid ) {
+ event_mask |= GLMediaEventListener.EVENT_CHANGE_VID;
+ this.vid = vid;
+ }
+ if( this.aid != aid ) {
+ event_mask |= GLMediaEventListener.EVENT_CHANGE_AID;
+ this.aid = aid;
+ }
if( this.width != width || this.height != height ) {
event_mask |= GLMediaEventListener.EVENT_CHANGE_SIZE;
this.width = width;
@@ -701,6 +975,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
if( this.fps != fps ) {
event_mask |= GLMediaEventListener.EVENT_CHANGE_FPS;
this.fps = fps;
+ this.frame_period = (int) ( 1000f / fps + 0.5f );
}
if( this.bps_stream != bps_stream || this.bps_video != bps_video || this.bps_audio != bps_audio ) {
event_mask |= GLMediaEventListener.EVENT_CHANGE_BPS;
@@ -708,9 +983,10 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
this.bps_video = bps_video;
this.bps_audio = bps_audio;
}
- if( this.totalFrames != totalFrames || this.duration != duration ) {
+ if( this.videoFrames != videoFrames || this.audioFrames != audioFrames || this.duration != duration ) {
event_mask |= GLMediaEventListener.EVENT_CHANGE_LENGTH;
- this.totalFrames = totalFrames;
+ this.videoFrames = videoFrames;
+ this.audioFrames = audioFrames;
this.duration = duration;
}
if( (null!=acodec && acodec.length()>0 && !this.acodec.equals(acodec)) ) {
@@ -736,78 +1012,120 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
}
@Override
- public final synchronized State destroy(GL gl) {
- destroyFramePusher();
- destroyImpl(gl);
- removeAllTextureFrames(gl);
- state = State.Uninitialized;
- return state;
+ public final State destroy(GL gl) {
+ synchronized( stateLock ) {
+ destroyFramePusher();
+ destroyImpl(gl);
+ removeAllTextureFrames(gl);
+ state = State.Uninitialized;
+ return state;
+ }
}
protected abstract void destroyImpl(GL gl);
@Override
- public final synchronized URLConnection getURLConnection() {
+ public final URLConnection getURLConnection() {
return urlConn;
}
@Override
- public final synchronized String getVideoCodec() {
+ public final int getVID() { return vid; }
+
+ @Override
+ public final int getAID() { return aid; }
+
+ @Override
+ public final String getVideoCodec() {
return vcodec;
}
@Override
- public final synchronized String getAudioCodec() {
+ public final String getAudioCodec() {
return acodec;
}
@Override
- public final synchronized long getTotalFrames() {
- return totalFrames;
+ public final int getVideoFrames() {
+ return videoFrames;
+ }
+
+ public final int getAudioFrames() {
+ return audioFrames;
}
@Override
- public final synchronized int getDuration() {
+ public final int getDuration() {
return duration;
}
@Override
- public final synchronized long getStreamBitrate() {
+ public final long getStreamBitrate() {
return bps_stream;
}
@Override
- public final synchronized int getVideoBitrate() {
+ public final int getVideoBitrate() {
return bps_video;
}
@Override
- public final synchronized int getAudioBitrate() {
+ public final int getAudioBitrate() {
return bps_audio;
}
@Override
- public final synchronized float getFramerate() {
+ public final float getFramerate() {
return fps;
}
@Override
- public final synchronized int getWidth() {
+ public final int getWidth() {
return width;
}
@Override
- public final synchronized int getHeight() {
+ public final int getHeight() {
return height;
}
@Override
- public final synchronized String toString() {
- final float ct = getCurrentPosition() / 1000.0f, tt = getDuration() / 1000.0f;
+ public final String toString() {
+ final float tt = getDuration() / 1000.0f;
final String loc = ( null != urlConn ) ? urlConn.getURL().toExternalForm() : "<undefined stream>" ;
- return "GLMediaPlayer["+state+", "+frameNumber+"/"+totalFrames+" frames, "+ct+"/"+tt+"s, speed "+playSpeed+", "+bps_stream+" bps, "+
- "Texture[count "+textureCount+", target "+toHexString(textureTarget)+", format "+toHexString(textureFormat)+", type "+toHexString(textureType)+"], "+
- "Stream[Video[<"+vcodec+">, "+width+"x"+height+", "+fps+" fps, "+bps_video+" bsp], "+
- "Audio[<"+acodec+">, "+bps_audio+" bsp]], "+loc+"]";
+ final int freeVideoFrames = null != videoFramesFree ? videoFramesFree.size() : 0;
+ final int decVideoFrames = null != videoFramesDecoded ? videoFramesDecoded.size() : 0;
+ return "GLMediaPlayer["+state+", frames[p "+presentedFrameCount+", d "+decodedFrameCount+", t "+videoFrames+" ("+tt+" s)], "+
+ "speed "+playSpeed+", "+bps_stream+" bps, "+
+ "Texture[count "+textureCount+", free "+freeVideoFrames+", dec "+decVideoFrames+", target "+toHexString(textureTarget)+", format "+toHexString(textureFormat)+", type "+toHexString(textureType)+"], "+
+ "Video[id "+vid+", <"+vcodec+">, "+width+"x"+height+", "+fps+" fps, "+bps_video+" bps], "+
+ "Audio[id "+aid+", <"+acodec+">, "+bps_audio+" bps, "+audioFrames+" frames], uri "+loc+"]";
+ }
+
+ @Override
+ public final String getPerfString() {
+ final int scr_pts = video_scr_pts +
+ (int) ( ( System.currentTimeMillis() - video_scr_t0 ) * playSpeed );
+ final int d_vpts = video_pts_last - scr_pts;
+ return getPerfStringImpl( scr_pts, video_pts_last, d_vpts, getVideoDPTSAvg() );
+ }
+ private final String getPerfStringImpl(final int scr_pts, final int video_pts, final int d_vpts, final int video_dpts_avg_diff) {
+ final float tt = getDuration() / 1000.0f;
+ final int audio_scr = (int) ( ( System.currentTimeMillis() - audio_scr_t0 ) * playSpeed );
+ final int audio_pts = getAudioPTSImpl();
+ final int d_apts = audio_pts - audio_scr;
+ 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()+"]";
+ } else {
+ audioSinkInfo = "";
+ }
+ final int freeVideoFrames = null != videoFramesFree ? videoFramesFree.size() : 0;
+ final int decVideoFrames = null != videoFramesDecoded ? videoFramesDecoded.size() : 0;
+ return state+", frames[p "+presentedFrameCount+", d "+decodedFrameCount+", t "+videoFrames+" ("+tt+" s)], "+
+ "speed " + playSpeed+", vSCR "+scr_pts+", 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+"]";
}
@Override
@@ -831,7 +1149,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
}
@Override
- public final synchronized GLMediaEventListener[] getEventListeners() {
+ public final GLMediaEventListener[] getEventListeners() {
synchronized(eventListenersLock) {
return eventListeners.toArray(new GLMediaEventListener[eventListeners.size()]);
}
diff --git a/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java
index f1ce42257..5d70ca33d 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java
@@ -38,6 +38,7 @@ import jogamp.opengl.util.av.GLMediaPlayerImpl;
import com.jogamp.common.nio.Buffers;
import com.jogamp.common.util.IOUtil;
+import com.jogamp.opengl.util.av.GLMediaPlayer;
import com.jogamp.opengl.util.texture.Texture;
import com.jogamp.opengl.util.texture.TextureData;
import com.jogamp.opengl.util.texture.TextureIO;
@@ -62,7 +63,7 @@ public class NullGLMediaPlayer extends GLMediaPlayerImpl {
}
@Override
- protected final boolean startImpl() {
+ protected final boolean playImpl() {
pos_start = (int)System.currentTimeMillis();
return true;
}
@@ -73,11 +74,6 @@ public class NullGLMediaPlayer extends GLMediaPlayerImpl {
}
@Override
- protected final boolean stopImpl() {
- return true;
- }
-
- @Override
protected final int seekImpl(int msec) {
pos_ms = msec;
validatePos();
@@ -86,20 +82,16 @@ public class NullGLMediaPlayer extends GLMediaPlayerImpl {
@Override
protected final boolean getNextTextureImpl(GL gl, TextureFrame nextFrame, boolean blocking) {
+ nextFrame.setPTS( getAudioPTSImpl() );
return true;
}
- @Override
- protected final void syncFrame2Audio(TextureFrame frame) { }
@Override
- protected final int getCurrentPositionImpl() {
+ protected final int getAudioPTSImpl() {
pos_ms = (int)System.currentTimeMillis() - pos_start;
validatePos();
return pos_ms;
}
- @Override
- protected final int getAudioPTSImpl() { return getCurrentPositionImpl(); }
-
@Override
protected final void destroyImpl(GL gl) {
@@ -110,7 +102,7 @@ public class NullGLMediaPlayer extends GLMediaPlayerImpl {
}
@Override
- protected final void initGLStreamImpl(GL gl) throws IOException {
+ protected final void initGLStreamImpl(GL gl, int vid, int aid) throws IOException {
try {
URLConnection urlConn = IOUtil.getResource("jogl/util/data/av/test-ntsc01-160x90.png", this.getClass().getClassLoader());
if(null != urlConn) {
@@ -136,13 +128,14 @@ public class NullGLMediaPlayer extends GLMediaPlayerImpl {
GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false,
false, false, buffer, null);
}
+ 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(_w, _h,
- 0, 0, 0,
- _fps, _totalFrames, _duration,
- "png-static", null);
+ updateAttributes(GLMediaPlayer.STREAM_ID_AUTO, r_aid,
+ _w, _h, 0,
+ 0, 0, _fps,
+ _totalFrames, 0, _duration, "png-static", null);
}
@Override
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 83a5960f1..dc7ceae39 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java
@@ -42,9 +42,11 @@ import com.jogamp.common.util.VersionNumber;
import com.jogamp.gluegen.runtime.ProcAddressTable;
import com.jogamp.opengl.util.GLPixelStorageModes;
import com.jogamp.opengl.util.av.AudioSink;
+import com.jogamp.opengl.util.av.AudioSink.AudioDataFormat;
+import com.jogamp.opengl.util.av.AudioSink.AudioDataType;
import com.jogamp.opengl.util.av.AudioSinkFactory;
+import com.jogamp.opengl.util.av.GLMediaPlayer;
import com.jogamp.opengl.util.texture.Texture;
-import com.jogamp.opengl.util.texture.TextureSequence.TextureFrame;
import jogamp.opengl.GLContextImpl;
import jogamp.opengl.util.av.GLMediaPlayerImpl;
@@ -136,13 +138,17 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl {
( vers >> 8 ) & 0xFF,
( vers >> 0 ) & 0xFF );
}
+
+ //
+ // General
+ //
+
+ protected long moviePtr = 0;
//
// Video
//
- protected long moviePtr = 0;
- protected GLPixelStorageModes psm;
protected PixelFormat vPixelFmt = null;
protected int vPlanes = 0;
protected int vBitsPerPixel = 0;
@@ -152,15 +158,17 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl {
protected int texWidth, texHeight; // overall (stuffing planes in one texture)
protected ByteBuffer texCopy;
protected String singleTexComp = "r";
+ protected GLPixelStorageModes psm;
//
// Audio
//
- protected final int AudioFrameCount = 8;
- protected final AudioSink audioSink;
- protected final int maxAvailableAudio;
- protected AudioSink.AudioDataFormat chosenAudioFormat;
+ protected static final int AFRAMES_PER_VFRAME = 8;
+ protected int aFrameCount = 0;
+ protected SampleFormat aSampleFmt = null;
+ protected AudioSink.AudioDataFormat avChosenAudioFormat;
+ protected AudioSink.AudioDataFormat sinkChosenAudioFormat;
public FFMPEGMediaPlayer() {
if(!available) {
@@ -171,12 +179,11 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl {
throw new GLException("Couldn't create FFMPEGInstance");
}
psm = new GLPixelStorageModes();
- audioSink = AudioSinkFactory.createDefault();
- maxAvailableAudio = audioSink.getQueuedByteCount();
+ audioSink = null;
}
@Override
protected final int validateTextureCount(int desiredTextureCount) {
- return desiredTextureCount>1 ? desiredTextureCount : 2;
+ return desiredTextureCount>2 ? Math.max(4, desiredTextureCount) : 2;
}
@Override
protected final boolean requiresOffthreadGLCtx() { return true; }
@@ -187,10 +194,18 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl {
destroyInstance0(moviePtr);
moviePtr = 0;
}
+ destroyAudioSink();
+ }
+ private final void destroyAudioSink() {
+ final AudioSink _audioSink = audioSink;
+ if( null != _audioSink ) {
+ audioSink = null;
+ _audioSink.destroy();
+ }
}
@Override
- protected final void initGLStreamImpl(GL gl) throws IOException {
+ protected final void initGLStreamImpl(GL gl, int vid, int aid) throws IOException {
if(0==moviePtr) {
throw new GLException("FFMPEG native instance null");
}
@@ -209,11 +224,32 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl {
}
final String urlS=urlConn.getURL().toExternalForm();
+
+ aFrameCount = AFRAMES_PER_VFRAME * textureCount + AFRAMES_PER_VFRAME/2;
- chosenAudioFormat = audioSink.initSink(audioSink.getPreferredFormat(), AudioFrameCount);
System.err.println("setURL: p1 "+this);
- setStream0(moviePtr, urlS, -1, -1, AudioFrameCount); // issues updateAttributes*(..)
- System.err.println("setURL: p2 "+this);
+ destroyAudioSink();
+ AudioSink _audioSink;
+ if( GLMediaPlayer.STREAM_ID_NONE == aid ) {
+ _audioSink = AudioSinkFactory.createNull();
+ } else {
+ _audioSink = AudioSinkFactory.createDefault();
+ }
+ final AudioDataFormat preferredAudioFormat = _audioSink.getPreferredFormat();
+ // setStream(..) issues updateAttributes*(..), and defines avChosenAudioFormat, vid, aid, .. etc
+ setStream0(moviePtr, urlS, vid, aid, aFrameCount, preferredAudioFormat.channelCount, preferredAudioFormat.sampleRate);
+ // final int audioBytesPerFrame = bps_audio/8000 * frame_period * textureCount;
+
+ System.err.println("setURL: p2 preferred "+preferredAudioFormat+", avChosen "+avChosenAudioFormat+", "+this);
+ sinkChosenAudioFormat = _audioSink.initSink(avChosenAudioFormat, aFrameCount);
+ System.err.println("setURL: p3 avChosen "+avChosenAudioFormat+", chosen "+sinkChosenAudioFormat);
+ if( null == sinkChosenAudioFormat ) {
+ System.err.println("AudioSink "+_audioSink.getClass().getName()+" does not support "+avChosenAudioFormat+", using Null");
+ _audioSink.destroy();
+ _audioSink = AudioSinkFactory.createNull();
+ sinkChosenAudioFormat = _audioSink.initSink(avChosenAudioFormat, aFrameCount);
+ }
+ audioSink = _audioSink;
int tf, tif=GL.GL_RGBA; // texture format and internal format
switch(vBytesPerPixelPerPlane) {
@@ -256,7 +292,8 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl {
private void updateAttributes2(int pixFmt, int planes, int bitsPerPixel, int bytesPerPixelPerPlane,
int lSz0, int lSz1, int lSz2,
- int tWd0, int tWd1, int tWd2) {
+ int tWd0, int tWd1, int tWd2,
+ int sampleFmt, int sampleRate, int channels) {
vPixelFmt = PixelFormat.valueOf(pixFmt);
vPlanes = planes;
vBitsPerPixel = bitsPerPixel;
@@ -286,12 +323,53 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl {
default: // FIXME: Add more planar formats !
throw new RuntimeException("Unsupported pixelformat: "+vPixelFmt);
}
+
+ aSampleFmt = SampleFormat.valueOf(sampleFmt);
+ final int sampleSize;
+ final boolean signed, fixedP;
+ switch( aSampleFmt ) {
+ case S32:
+ case S32P:
+ sampleSize = 32;
+ signed = true;
+ fixedP = true;
+ break;
+ case S16:
+ case S16P:
+ sampleSize = 16;
+ signed = true;
+ fixedP = true;
+ break;
+ case U8:
+ case U8P:
+ sampleSize = 8;
+ signed = false;
+ fixedP = true;
+ break;
+ case DBL:
+ case DBLP:
+ sampleSize = 64;
+ signed = true;
+ fixedP = true;
+ break;
+ case FLT:
+ case FLTP:
+ sampleSize = 32;
+ signed = true;
+ fixedP = true;
+ break;
+ default: // FIXME: Add more planar formats !
+ throw new RuntimeException("Unsupported sampleformat: "+aSampleFmt);
+ }
+ avChosenAudioFormat = new AudioDataFormat(AudioDataType.PCM, sampleRate, sampleSize, channels, signed, fixedP, true /* littleEndian */);
+
if(DEBUG) {
- System.err.println("XXX0: fmt "+vPixelFmt+", planes "+vPlanes+", bpp "+vBitsPerPixel+"/"+vBytesPerPixelPerPlane);
+ System.err.println("audio: fmt "+aSampleFmt+", "+avChosenAudioFormat);
+ System.err.println("video: fmt "+vPixelFmt+", planes "+vPlanes+", bpp "+vBitsPerPixel+"/"+vBytesPerPixelPerPlane);
for(int i=0; i<3; i++) {
- System.err.println("XXX0 "+i+": "+vTexWidth[i]+"/"+vLinesize[i]);
+ System.err.println("video: "+i+": "+vTexWidth[i]+"/"+vLinesize[i]);
}
- System.err.println("XXX0 total tex "+texWidth+"x"+texHeight);
+ System.err.println("video: total tex "+texWidth+"x"+texHeight);
}
}
@@ -355,54 +433,27 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl {
}
@Override
- protected final synchronized int getCurrentPositionImpl() {
- return 0!=moviePtr ? getVideoPTS0(moviePtr) : 0;
- }
-
- @Override
- public final int getAudioPTSImpl() { return 0; }
-
- @Override
- protected final synchronized boolean setPlaySpeedImpl(float rate) {
- return true;
- }
-
- @Override
- public final synchronized boolean startImpl() {
+ public final boolean playImpl() {
if(0==moviePtr) {
return false;
}
return true;
}
- /** @return time position after issuing the command */
@Override
- public final synchronized boolean pauseImpl() {
+ public final boolean pauseImpl() {
if(0==moviePtr) {
return false;
}
return true;
}
- /** @return time position after issuing the command */
- @Override
- public final synchronized boolean stopImpl() {
- if(0==moviePtr) {
- return false;
- }
- return true;
- }
-
- /** @return time position after issuing the command */
@Override
protected final synchronized int seekImpl(int msec) {
if(0==moviePtr) {
throw new GLException("FFMPEG native instance null");
}
- int pts0 = getVideoPTS0(moviePtr);
- int pts1 = seek0(moviePtr, msec);
- System.err.println("Seek: "+pts0+" -> "+msec+" : "+pts1);
- return pts1;
+ return seek0(moviePtr, msec);
}
@Override
@@ -427,7 +478,6 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl {
psm.restore(gl);
}
if( 0 < avPTS ) {
- vSTS = avPTS;
nextFrame.setPTS(avPTS);
return true;
} else {
@@ -436,50 +486,15 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl {
}
private final void pushSound(ByteBuffer sampleData, int data_size, int audio_pts) {
- aSTS = audio_pts;
- final AudioSink.AudioFrame frame = new AudioSink.AudioFrame(sampleData, data_size, audio_pts);
- if( audioSink.isDataAvailable(frame.dataSize) ) {
- audioSink.writeData(frame);
+ setFirstAudioPTS2SCR( audio_pts );
+ if( 1.0f == playSpeed || audioSinkPlaySpeedSet ) {
+ audioSink.enqueueData( new AudioSink.AudioFrame(sampleData, data_size, audio_pts ) );
}
}
-
- /** last audio streaming TS */
- private int aSTS = 0;
- /** last video streaming TS */
- private int vSTS = 0;
-
- private long lastAudioTime = 0;
- private static final int audio_dt_d = 400;
- private long lastVideoTime = 0;
- private static final int video_dt_d = 9;
-
+
@Override
- protected final void syncFrame2Audio(TextureFrame frame) {
- /**
- // poor mans video sync .. TODO: off thread 'readNextPackage0(..)' on shared GLContext and multi textures/unit!
- final long now = System.currentTimeMillis();
- // Try sync video to audio
- final long now_d = now - lastAudioTime;
- final long pts_d = vSTS - aSTS - 444; // hack 444 == play video 444ms ahead of audio
- final long dt = Math.min(47, (long) ( (float) ( pts_d - now_d ) / getPlaySpeed() ) ) ;
- //final long dt = (long) ( (float) ( pts_d - now_d ) / getPlaySpeed() ) ;
- final boolean sleep = dt>video_dt_d && dt<1000 && audioSink.getQueuedByteCount()<maxAvailableAudio-10000;
- final long sleepP = dt-video_dt_d;
- if(DEBUG) {
- final int qAT = audioSink.getQueuedTime();
- System.err.println("s: pts-v "+vSTS+", qAT "+qAT+", pts-d "+pts_d+", now_d "+now_d+", dt "+dt+", sleep "+sleep+", sleepP "+sleepP+" ms");
- }
- // ?? Maybe use audioSink.getQueuedTime();
- if( sleep ) {
- try {
- Thread.sleep(sleepP);
- } catch (InterruptedException e) { }
- lastVideoTime = System.currentTimeMillis();
- } else {
- lastVideoTime = now;
- }
- */
- }
+ protected final boolean syncAVRequired() { return true; }
+
private static native int getAvUtilVersion0();
private static native int getAvFormatVersion0();
private static native int getAvCodecVersion0();
@@ -488,10 +503,17 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl {
private native void destroyInstance0(long moviePtr);
/**
- * Issues {@link #updateAttributes(int, int, int, int, int, float, int, int, String, String)}
+ * Issues {@link #updateAttributes(int, int, int, int, int, int, int, float, int, int, String, String)}
* and {@link #updateAttributes2(int, int, int, int, int, int, int, int, int, int)}.
+ * <p>
+ * Always uses {@link AudioSink.AudioDataFormat}:
+ * <pre>
+ * [type PCM, sampleRate [10000(?)..44100..48000], sampleSize 16, channelCount 1-2, signed, littleEndian]
+ * </pre>
+ * </p>
*/
- private native void setStream0(long moviePtr, String url, int vid, int aid, int audioFrameCount);
+ private native void setStream0(long moviePtr, String url, int vid, int aid, int audioFrameCount,
+ int aChannelCount, int aSampleRate);
private native void setGLFuncs0(long moviePtr, long procAddrGLTexSubImage2D, long procAddrGLGetError);
private native int getVideoPTS0(long moviePtr);
@@ -505,6 +527,32 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl {
private native int readNextPacket0(long moviePtr, int texTarget, int texFmt, int texType);
private native int seek0(long moviePtr, int position);
+
+ public static enum SampleFormat {
+ // NONE = -1,
+ U8, ///< unsigned 8 bits
+ S16, ///< signed 16 bits
+ S32, ///< signed 32 bits
+ FLT, ///< float
+ DBL, ///< double
+
+ U8P, ///< unsigned 8 bits, planar
+ S16P, ///< signed 16 bits, planar
+ S32P, ///< signed 32 bits, planar
+ FLTP, ///< float, planar
+ DBLP, ///< double, planar
+
+ COUNT; ///< Number of sample formats.
+
+ public static SampleFormat valueOf(int i) {
+ for (SampleFormat fmt : SampleFormat.values()) {
+ if(fmt.ordinal() == i) {
+ return fmt;
+ }
+ }
+ return null;
+ }
+ };
public static enum PixelFormat {
// NONE= -1,
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 a21bb40a8..d03cad28a 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/impl/OMXGLMediaPlayer.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/impl/OMXGLMediaPlayer.java
@@ -88,15 +88,16 @@ public class OMXGLMediaPlayer extends EGLMediaPlayerImpl {
@Override
protected void destroyImpl(GL gl) {
- _detachVideoRenderer(moviePtr);
if (moviePtr != 0) {
+ _stop(moviePtr);
+ _detachVideoRenderer(moviePtr);
_destroyInstance(moviePtr);
moviePtr = 0;
}
}
@Override
- protected void initGLStreamImpl(GL gl) throws IOException {
+ protected void initGLStreamImpl(GL gl, int vid, int aid) throws IOException {
if(0==moviePtr) {
throw new GLException("OMX native instance null");
}
@@ -113,12 +114,8 @@ public class OMXGLMediaPlayer extends EGLMediaPlayerImpl {
}
@Override
- protected int getCurrentPositionImpl() {
- return 0!=moviePtr ? _getCurrentPosition(moviePtr) : 0;
- }
- @Override
protected int getAudioPTSImpl() {
- return getCurrentPositionImpl();
+ return 0!=moviePtr ? _getCurrentPosition(moviePtr) : 0;
}
@Override
@@ -131,7 +128,7 @@ public class OMXGLMediaPlayer extends EGLMediaPlayerImpl {
}
@Override
- public synchronized boolean startImpl() {
+ public synchronized boolean playImpl() {
if(0==moviePtr) {
return false;
}
@@ -151,16 +148,6 @@ public class OMXGLMediaPlayer extends EGLMediaPlayerImpl {
/** @return time position after issuing the command */
@Override
- public synchronized boolean stopImpl() {
- if(0==moviePtr) {
- return false;
- }
- _stop(moviePtr);
- return true;
- }
-
- /** @return time position after issuing the command */
- @Override
protected int seekImpl(int msec) {
if(0==moviePtr) {
throw new GLException("OMX native instance null");
@@ -184,8 +171,6 @@ public class OMXGLMediaPlayer extends EGLMediaPlayerImpl {
}
return true;
}
- @Override
- protected void syncFrame2Audio(TextureFrame frame) { }
private String replaceAll(String orig, String search, String repl) {
String dest=null;
diff --git a/src/jogl/native/libav/ffmpeg_tool.h b/src/jogl/native/libav/ffmpeg_tool.h
index 2dff1110c..081e17323 100644
--- a/src/jogl/native/libav/ffmpeg_tool.h
+++ b/src/jogl/native/libav/ffmpeg_tool.h
@@ -58,6 +58,18 @@ typedef GLenum (APIENTRYP PFNGLGETERRORPROC) (void);
*/
#define AV_TIME_BASE_MSEC (AV_TIME_BASE/1000)
+#define AV_VERSION_MAJOR(i) ( ( i >> 16 ) & 0xFF )
+#define AV_VERSION_MINOR(i) ( ( i >> 8 ) & 0xFF )
+#define AV_VERSION_SUB(i) ( ( i >> 0 ) & 0xFF )
+
+/** Sync w/ GLMediaPlayer.STREAM_ID_NONE */
+#define AV_STREAM_ID_NONE -2
+
+/** Sync w/ GLMediaPlayer.STREAM_ID_AUTO */
+#define AV_STREAM_ID_AUTO -1
+
+#define AV_HAS_API_REQUEST_CHANNELS(pAV) (AV_VERSION_MAJOR(pAV->avcodecVersion) < 55)
+
static inline float my_av_q2f(AVRational a){
return a.num / (float) a.den;
}
@@ -68,6 +80,10 @@ static inline int32_t my_av_q2i32(int32_t snum, AVRational a){
typedef struct {
int32_t verbose;
+ uint32_t avcodecVersion;
+ uint32_t avformatVersion;
+ uint32_t avutilVersion;
+
PFNGLTEXSUBIMAGE2DPROC procAddrGLTexSubImage2D;
PFNGLGETERRORPROC procAddrGLGetError;
@@ -103,7 +119,8 @@ typedef struct {
int32_t bps_stream; // bits per seconds
int32_t bps_video; // bits per seconds
int32_t bps_audio; // bits per seconds
- int32_t totalFrames;
+ int32_t frames_video;
+ int32_t frames_audio;
int32_t duration; // msec
int32_t start_time; // msec
diff --git a/src/jogl/native/libav/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c b/src/jogl/native/libav/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c
index 346ba6c07..bc376cebd 100644
--- a/src/jogl/native/libav/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c
+++ b/src/jogl/native/libav/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c
@@ -31,6 +31,7 @@
#include "JoglCommon.h"
#include "ffmpeg_tool.h"
#include <libavutil/pixdesc.h>
+#include <libavutil/samplefmt.h>
#include <GL/gl.h>
static const char * const ClazzNameFFMPEGMediaPlayer = "jogamp/opengl/util/av/impl/FFMPEGMediaPlayer";
@@ -204,17 +205,18 @@ static void _updateJavaAttributes(JNIEnv *env, jobject instance, FFMPEGToolBasic
}
(*env)->CallVoidMethod(env, instance, jni_mid_updateAttributes1,
+ pAV->vid, pAV->aid,
w, h,
pAV->bps_stream, pAV->bps_video, pAV->bps_audio,
- pAV->fps, (int32_t)((pAV->duration/1000)*pAV->fps), pAV->duration,
+ pAV->fps, pAV->frames_video, pAV->frames_audio, pAV->duration,
(*env)->NewStringUTF(env, pAV->vcodec),
(*env)->NewStringUTF(env, pAV->acodec) );
(*env)->CallVoidMethod(env, instance, jni_mid_updateAttributes2,
pAV->vPixFmt, pAV->vBufferPlanes,
pAV->vBitsPerPixel, pAV->vBytesPerPixelPerPlane,
pAV->vLinesize[0], pAV->vLinesize[1], pAV->vLinesize[2],
- pAV->vTexWidth[0], pAV->vTexWidth[1], pAV->vTexWidth[2]);
- // JoglCommon_ReleaseJNIEnv (shallBeDetached);
+ pAV->vTexWidth[0], pAV->vTexWidth[1], pAV->vTexWidth[2],
+ pAV->aSampleFmt, pAV->aSampleRate, pAV->aChannels);
}
}
@@ -337,8 +339,8 @@ JNIEXPORT jboolean JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_ini
}
jni_mid_pushSound = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "pushSound", "(Ljava/nio/ByteBuffer;II)V");
- jni_mid_updateAttributes1 = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "updateAttributes", "(IIIIIFIILjava/lang/String;Ljava/lang/String;)V");
- jni_mid_updateAttributes2 = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "updateAttributes2", "(IIIIIIIIII)V");
+ jni_mid_updateAttributes1 = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "updateAttributes", "(IIIIIIIFIIILjava/lang/String;Ljava/lang/String;)V");
+ jni_mid_updateAttributes2 = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "updateAttributes2", "(IIIIIIIIIIIII)V");
if(jni_mid_pushSound == NULL ||
jni_mid_updateAttributes1 == NULL ||
@@ -356,6 +358,10 @@ JNIEXPORT jlong JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_create
JoglCommon_throwNewRuntimeException(env, "Couldn't alloc instance");
return 0;
}
+ pAV->avcodecVersion = sp_avcodec_version();
+ pAV->avformatVersion = sp_avformat_version();
+ pAV->avutilVersion = sp_avutil_version();
+
// Register all formats and codecs
sp_av_register_all();
// Network too ..
@@ -364,8 +370,8 @@ JNIEXPORT jlong JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_create
}
pAV->verbose = verbose;
- pAV->vid=-1;
- pAV->aid=-1;
+ pAV->vid=AV_STREAM_ID_AUTO;
+ pAV->aid=AV_STREAM_ID_AUTO;
return (jlong) (intptr_t) pAV;
}
@@ -380,8 +386,23 @@ JNIEXPORT void JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_destroy
}
}
+static uint64_t getDefaultAudioChannelLayout(int channelCount) {
+ switch(channelCount) {
+ case 1: return AV_CH_LAYOUT_MONO;
+ case 2: return AV_CH_LAYOUT_STEREO;
+ case 3: return AV_CH_LAYOUT_SURROUND;
+ case 4: return AV_CH_LAYOUT_QUAD;
+ case 5: return AV_CH_LAYOUT_5POINT0;
+ case 6: return AV_CH_LAYOUT_5POINT1;
+ case 7: return AV_CH_LAYOUT_6POINT1;
+ case 8: return AV_CH_LAYOUT_7POINT1;
+ default: return AV_CH_LAYOUT_NATIVE;
+ }
+}
+
JNIEXPORT void JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_setStream0
- (JNIEnv *env, jobject instance, jlong ptr, jstring jURL, jint vid, jint aid, jint audioFrameCount)
+ (JNIEnv *env, jobject instance, jlong ptr, jstring jURL, jint vid, jint aid, jint audioFrameCount,
+ jint aChannelCount, jint aSampleRate)
{
int res, i;
jboolean iscopy;
@@ -434,27 +455,39 @@ JNIEXPORT void JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_setStre
pAV->bps_stream = pAV->pFormatCtx->bit_rate;
}
- fprintf(stderr, "Streams: %d\n", pAV->pFormatCtx->nb_streams); // JAU
+ if(pAV->verbose) {
+ fprintf(stderr, "Streams: %d, req vid %d aid %d\n", pAV->pFormatCtx->nb_streams, vid, aid);
+ }
// Find the first audio and video stream, or the one matching vid
// FIXME: Libav Binary compatibility! JAU01
- for(i=0; ( -1==pAV->aid || -1==pAV->vid ) && i<pAV->pFormatCtx->nb_streams; i++) {
+ for(i=0; ( AV_STREAM_ID_AUTO==pAV->aid || AV_STREAM_ID_AUTO==pAV->vid ) && i<pAV->pFormatCtx->nb_streams; i++) {
AVStream *st = pAV->pFormatCtx->streams[i];
- fprintf(stderr, "Stream: %d: is-video %d, is-audio %d\n", i, (AVMEDIA_TYPE_VIDEO == st->codec->codec_type), AVMEDIA_TYPE_AUDIO == st->codec->codec_type); // JAU
+ if(pAV->verbose) {
+ fprintf(stderr, "Stream: %d: is-video %d, is-audio %d\n", i, (AVMEDIA_TYPE_VIDEO == st->codec->codec_type), AVMEDIA_TYPE_AUDIO == st->codec->codec_type);
+ }
if(AVMEDIA_TYPE_VIDEO == st->codec->codec_type) {
- if(-1==pAV->vid && (-1==vid || vid == i) ) {
+ if(AV_STREAM_ID_AUTO==pAV->vid && (AV_STREAM_ID_AUTO==vid || vid == i) ) {
pAV->pVStream = st;
pAV->vid=i;
}
} else if(AVMEDIA_TYPE_AUDIO == st->codec->codec_type) {
- if(-1==pAV->aid && (-1==aid || aid == i) ) {
+ if(AV_STREAM_ID_AUTO==pAV->aid && (AV_STREAM_ID_AUTO==aid || aid == i) ) {
pAV->pAStream = st;
pAV->aid=i;
}
}
}
+ if( AV_STREAM_ID_AUTO == pAV->aid ) {
+ pAV->aid = AV_STREAM_ID_NONE;
+ }
+ if( AV_STREAM_ID_AUTO == pAV->vid ) {
+ pAV->vid = AV_STREAM_ID_NONE;
+ }
- fprintf(stderr, "Found vid %d, aid %d\n", pAV->vid, pAV->aid); // JAU
+ if( pAV->verbose ) {
+ fprintf(stderr, "Found vid %d, aid %d\n", pAV->vid, pAV->aid);
+ }
if(0<=pAV->aid) {
// Get a pointer to the codec context for the audio stream
@@ -465,6 +498,23 @@ JNIEXPORT void JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_setStre
if (pAV->pACodecCtx->bit_rate) {
pAV->bps_audio = pAV->pACodecCtx->bit_rate;
}
+
+ // Customize ..
+ // pAV->pACodecCtx->thread_count=2;
+ // pAV->pACodecCtx->thread_type=FF_THREAD_FRAME|FF_THREAD_SLICE; // Decode more than one frame at once
+ pAV->pACodecCtx->thread_count=1;
+ pAV->pACodecCtx->thread_type=0;
+ pAV->pACodecCtx->workaround_bugs=FF_BUG_AUTODETECT;
+ pAV->pACodecCtx->skip_frame=AVDISCARD_DEFAULT;
+
+ pAV->pACodecCtx->request_channel_layout=getDefaultAudioChannelLayout(aChannelCount);
+ if( AV_HAS_API_REQUEST_CHANNELS(pAV) && 1 <= aChannelCount && aChannelCount <= 2 ) {
+ pAV->pACodecCtx->request_channels=aChannelCount;
+ }
+ pAV->pACodecCtx->request_sample_fmt=AV_SAMPLE_FMT_S16;
+ // ignored: aSampleRate !
+ pAV->pACodecCtx->skip_frame=AVDISCARD_DEFAULT;
+
sp_avcodec_string(pAV->acodec, sizeof(pAV->acodec), pAV->pACodecCtx, 0);
// Find the decoder for the audio stream
@@ -489,8 +539,17 @@ JNIEXPORT void JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_setStre
// FIXME: Libav Binary compatibility! JAU01
pAV->aSampleRate = pAV->pACodecCtx->sample_rate;
pAV->aChannels = pAV->pACodecCtx->channels;
- pAV->aFrameSize = pAV->pACodecCtx->frame_size;
+ pAV->aFrameSize = pAV->pACodecCtx->frame_size; // in samples!
pAV->aSampleFmt = pAV->pACodecCtx->sample_fmt;
+ pAV->frames_audio = pAV->pAStream->nb_frames;
+
+ if( pAV->verbose ) {
+ fprintf(stderr, "A channels %d, sample_rate %d, frame_size %d, frame_number %d, r_frame_rate %f, avg_frame_rate %f, nb_frames %d, \n",
+ pAV->aChannels, pAV->aSampleRate, pAV->aFrameSize, pAV->pACodecCtx->frame_number,
+ my_av_q2f(pAV->pAStream->r_frame_rate),
+ my_av_q2f(pAV->pAStream->avg_frame_rate),
+ pAV->pAStream->nb_frames);
+ }
pAV->aFrameCount = audioFrameCount;
pAV->pAFrames = calloc(audioFrameCount, sizeof(AVFrame*));
@@ -516,6 +575,15 @@ JNIEXPORT void JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_setStre
// FIXME: Libav Binary compatibility! JAU01
pAV->bps_video = pAV->pVCodecCtx->bit_rate;
}
+
+ // Customize ..
+ // pAV->pVCodecCtx->thread_count=2;
+ // pAV->pVCodecCtx->thread_type=FF_THREAD_FRAME|FF_THREAD_SLICE; // Decode more than one frame at once
+ pAV->pVCodecCtx->thread_count=1;
+ pAV->pVCodecCtx->thread_type=0;
+ pAV->pVCodecCtx->workaround_bugs=FF_BUG_AUTODETECT;
+ pAV->pVCodecCtx->skip_frame=AVDISCARD_DEFAULT;
+
sp_avcodec_string(pAV->vcodec, sizeof(pAV->vcodec), pAV->pVCodecCtx, 0);
// Find the decoder for the video stream
@@ -542,8 +610,21 @@ JNIEXPORT void JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_setStre
pAV->pVCodecCtx->time_base.den=1000;
}
// FIXME: Libav Binary compatibility! JAU01
- pAV->fps = my_av_q2f(pAV->pVStream->avg_frame_rate);
+ if( 0 < pAV->pVStream->avg_frame_rate.den ) {
+ pAV->fps = my_av_q2f(pAV->pVStream->avg_frame_rate);
+ } else {
+ pAV->fps = my_av_q2f(pAV->pVStream->r_frame_rate);
+ }
+ pAV->frames_video = pAV->pVStream->nb_frames;
+ if( pAV->verbose ) {
+ fprintf(stderr, "V frame_size %d, frame_number %d, r_frame_rate %f %d/%d, avg_frame_rate %f %d/%d, nb_frames %d, \n",
+ pAV->pVCodecCtx->frame_size, pAV->pVCodecCtx->frame_number,
+ my_av_q2f(pAV->pVStream->r_frame_rate), pAV->pVStream->r_frame_rate.num, pAV->pVStream->r_frame_rate.den,
+ my_av_q2f(pAV->pVStream->avg_frame_rate), pAV->pVStream->avg_frame_rate.num, pAV->pVStream->avg_frame_rate.den,
+ pAV->pVStream->nb_frames);
+ }
+
// Allocate video frames
// FIXME: Libav Binary compatibility! JAU01
pAV->vPixFmt = pAV->pVCodecCtx->pix_fmt;
@@ -589,6 +670,14 @@ JNIEXPORT void JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_setGLFu
pAV->procAddrGLGetError = (PFNGLGETERRORPROC) (intptr_t)jProcAddrGLGetError;
}
+#if 0
+#define DBG_TEXSUBIMG2D_a(c,p,i) fprintf(stderr, "TexSubImage2D.%c offset %d / %d, size %d x %d, ", c, p->pVCodecCtx->width, p->pVCodecCtx->height/2, p->vTexWidth[i], p->pVCodecCtx->height/2)
+#define DBG_TEXSUBIMG2D_b(p) fprintf(stderr, "err 0x%X\n", pAV->procAddrGLGetError())
+#else
+#define DBG_TEXSUBIMG2D_a(c,p,i)
+#define DBG_TEXSUBIMG2D_b(p)
+#endif
+
JNIEXPORT jint JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_readNextPacket0
(JNIEnv *env, jobject instance, jlong ptr, jint texTarget, jint texFmt, jint texType)
{
@@ -607,11 +696,10 @@ JNIEXPORT jint JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_readNex
}
AVFrame* pAFrameCurrent = pAV->pAFrames[pAV->aFrameCurrent];
pAV->aFrameCurrent = ( pAV->aFrameCurrent + 1 ) % pAV->aFrameCount ;
- int new_packet = 1;
- int len1;
+ int frameCount;
int flush_complete = 0;
- while (packet.size > 0 || (!packet.data && new_packet)) {
- new_packet = 0;
+ for ( frameCount=0; 0 < packet.size || 0 == frameCount; frameCount++ ) {
+ int len1;
if (flush_complete) {
break;
}
@@ -640,7 +728,7 @@ JNIEXPORT jint JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_readNex
continue;
}
- int data_size = 0;
+ int32_t data_size = 0;
if(HAS_FUNC(sp_av_samples_get_buffer_size)) {
data_size = sp_av_samples_get_buffer_size(NULL /* linesize, may be NULL */,
pAV->aChannels,
@@ -648,16 +736,25 @@ JNIEXPORT jint JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_readNex
pAFrameCurrent->format,
1 /* align */);
}
- int32_t pts = (int64_t) ( pAFrameCurrent->pkt_pts * (int64_t) 1000 * (int64_t) pAV->pAStream->time_base.num )
- / (int64_t) pAV->pAStream->time_base.den;
#if 0
- printf("channels %d sample_rate %d \n", pAV->aChannels , pAV->aSampleRate);
- printf("data %d \n", pAV->aFrameSize);
+ fprintf(stderr, "channels %d sample_rate %d \n", pAV->aChannels , pAV->aSampleRate);
+ fprintf(stderr, "data %d \n", pAV->aFrameSize);
#endif
- pAV->aPTS += (int64_t) ( data_size * (int64_t) 1000 )
- / (int64_t) (2 * (int64_t) pAV->aChannels * (int64_t) pAV->aSampleRate);
+
+ const AVRational time_base = pAV->pAStream->time_base;
+ const int64_t pkt_pts = pAFrameCurrent->pkt_pts;
+ int aptsMode;
+ if( 0 == frameCount && AV_NOPTS_VALUE != pkt_pts ) { // 1st frame only, discard invalid PTS ..
+ pAV->aPTS = (pkt_pts * (int64_t) 1000 * (int64_t) time_base.num) / (int64_t) time_base.den ;
+ aptsMode = 0;
+ } else { // subsequent frames or invalid PTS ..
+ const int32_t bytesPerSample = 2; // av_get_bytes_per_sample( pAV->pACodecCtx->sample_fmt );
+ pAV->aPTS += data_size / ( pAV->aChannels * bytesPerSample * ( pAV->aSampleRate / 1000 ) );
+ aptsMode = 1;
+ }
if( pAV->verbose ) {
- printf("A pts %d - %d\n", pts, pAV->aPTS);
+ fprintf(stderr, "A pts %d [pkt_pts %ld, pkt_dts %ld], dataSize %d, f# %d, pts-mode %d\n",
+ pAV->aPTS, pkt_pts, pAFrameCurrent->pkt_dts, data_size, frameCount, aptsMode);
}
if( NULL != env ) {
jobject jSampleData = (*env)->NewDirectByteBuffer(env, pAFrameCurrent->data[0], data_size);
@@ -671,19 +768,14 @@ JNIEXPORT jint JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_readNex
sp_av_free_packet(&packet);
return 0;
}
-
- int new_packet = 1;
- int len1;
+ int frameCount;
int flush_complete = 0;
- while (packet.size > 0 || (!packet.data && new_packet)) {
-
- new_packet = 0;
+ for ( frameCount=0; 0 < packet.size || 0 == frameCount; frameCount++ ) {
+ int len1;
if (flush_complete) {
break;
}
-
len1 = sp_avcodec_decode_video2(pAV->pVCodecCtx, pAV->pVFrame, &frameFinished, &packet);
-
if (len1 < 0) {
// if error, we skip the frame
packet.size = 0;
@@ -702,71 +794,46 @@ JNIEXPORT jint JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_readNex
// FIXME: Libav Binary compatibility! JAU01
const AVRational time_base = pAV->pVStream->time_base;
- const int64_t pts = pAV->pVFrame->pkt_pts;
- if(AV_NOPTS_VALUE != pts) { // discard invalid PTS ..
- pAV->vPTS = (pts * (int64_t) 1000 * (int64_t) time_base.num) / (int64_t) time_base.den ;
-
- #if 0
- printf("PTS %d = %ld * ( ( 1000 * %ld ) / %ld ) '1000 * time_base', time_base = %lf\n",
- pAV->vPTS, pAV->pVFrame->pkt_pts, time_base.num, time_base.den, (time_base.num/(double)time_base.den));
- #endif
+ const int64_t pkt_pts = pAV->pVFrame->pkt_pts;
+ if(AV_NOPTS_VALUE != pkt_pts) { // discard invalid PTS ..
+ int32_t vPTS2 = (pAV->pVFrame->pkt_dts * (int64_t) 1000 * (int64_t) time_base.num) / (int64_t) time_base.den ;
+ pAV->vPTS = (pkt_pts * (int64_t) 1000 * (int64_t) time_base.num) / (int64_t) time_base.den ;
+ if( pAV->verbose ) {
+ fprintf(stderr, "V pts %d [pkt_pts %ld], pts2 %d [pkt_dts %ld]\n", pAV->vPTS, pkt_pts, vPTS2, pAV->pVFrame->pkt_dts);
+ }
+ } else {
+ if( pAV->verbose ) {
+ fprintf(stderr, "V pts ?? [pkt_pts %ld], pts2 ?? [pkt_dts %ld]\n", pkt_pts, pAV->pVFrame->pkt_dts);
+ }
}
resPTS = pAV->vPTS; // Video Frame!
- #if 0
- printf("tex2D codec %dx%d - frame %dx%d - width %d tex / %d linesize, pixfmt 0x%X, texType 0x%x, texTarget 0x%x\n",
- pAV->pVCodecCtx->width, pAV->pVCodecCtx->height,
- pAV->pVFrame->width, pAV->pVFrame->height, pAV->vTexWidth[0], pAV->pVFrame->linesize[0],
- texFmt, texType, texTarget);
- #endif
-
// 1st plane or complete packed frame
// FIXME: Libav Binary compatibility! JAU01
- #if 0
- GLenum glerr = pAV->procAddrGLGetError();
- printf("TexSubImage2D.1 texTarget 0x%x, offset %d / %d, size %d x %d, fmt 0x%X, type 0x%X, pre-err 0x%X, ",
- texTarget, 0, 0, pAV->vTexWidth[0], pAV->pVCodecCtx->height, texFmt, texType, glerr);
- #endif
+ DBG_TEXSUBIMG2D_a('Y',pAV,0);
pAV->procAddrGLTexSubImage2D(texTarget, 0,
0, 0,
pAV->vTexWidth[0], pAV->pVCodecCtx->height,
texFmt, texType, pAV->pVFrame->data[0]);
- #if 0
- glerr = pAV->procAddrGLGetError();
- printf("err 0x%X\n", glerr);
- #endif
+ DBG_TEXSUBIMG2D_b(pAV);
if(pAV->vPixFmt == PIX_FMT_YUV420P) {
// U plane
// FIXME: Libav Binary compatibility! JAU01
- #if 0
- printf("TexSubImage2D.U texTarget 0x%x, offset %d / %d, size %d x %d, fmt 0x%X, type 0x%X, ",
- texTarget, pAV->pVCodecCtx->width, 0, pAV->vTexWidth[1], pAV->pVCodecCtx->height/2,
- texFmt, texType);
- #endif
+ DBG_TEXSUBIMG2D_a('U',pAV,1);
pAV->procAddrGLTexSubImage2D(texTarget, 0,
pAV->pVCodecCtx->width, 0,
pAV->vTexWidth[1], pAV->pVCodecCtx->height/2,
texFmt, texType, pAV->pVFrame->data[1]);
- #if 0
- glerr = pAV->procAddrGLGetError();
- printf("err 0x%X\n", glerr);
- #endif
+ DBG_TEXSUBIMG2D_b(pAV);
// V plane
// FIXME: Libav Binary compatibility! JAU01
- #if 0
- printf("TexSubImage2D.V texTarget 0x%x, offset %d / %d, size %d x %d, fmt 0x%X, type 0x%X, ",
- texTarget, pAV->pVCodecCtx->width, pAV->pVCodecCtx->height/2, pAV->vTexWidth[2], pAV->pVCodecCtx->height/2,
- texFmt, texType);
- #endif
+ DBG_TEXSUBIMG2D_a('V',pAV,2);
pAV->procAddrGLTexSubImage2D(texTarget, 0,
pAV->pVCodecCtx->width, pAV->pVCodecCtx->height/2,
pAV->vTexWidth[2], pAV->pVCodecCtx->height/2,
texFmt, texType, pAV->pVFrame->data[2]);
- #if 0
- glerr = pAV->procAddrGLGetError();
- printf("err 0x%X\n", glerr);
- #endif
+ DBG_TEXSUBIMG2D_b(pAV);
} // FIXME: Add more planar formats !
}
}
diff --git a/src/test/com/jogamp/opengl/test/android/MovieCubeActivity0.java b/src/test/com/jogamp/opengl/test/android/MovieCubeActivity0.java
index 3e61e509c..e905bfeab 100644
--- a/src/test/com/jogamp/opengl/test/android/MovieCubeActivity0.java
+++ b/src/test/com/jogamp/opengl/test/android/MovieCubeActivity0.java
@@ -44,6 +44,7 @@ import com.jogamp.newt.opengl.GLWindow;
import com.jogamp.opengl.test.junit.jogl.demos.es2.av.MovieCube;
import com.jogamp.opengl.util.Animator;
+import com.jogamp.opengl.util.av.GLMediaPlayer;
import android.os.Bundle;
import android.util.Log;
@@ -84,7 +85,7 @@ public class MovieCubeActivity0 extends NewtBaseActivity {
final Animator animator = new Animator();
// Main
- final MovieCube demoMain = new MovieCube(urlConnection0, -2.3f, 0f, 0f);
+ final MovieCube demoMain = new MovieCube(urlConnection0, GLMediaPlayer.STREAM_ID_AUTO, GLMediaPlayer.STREAM_ID_AUTO, -2.3f, 0f, 0f);
final GLWindow glWindowMain = GLWindow.create(scrn, capsMain);
glWindowMain.setFullscreen(true);
setContentView(getWindow(), glWindowMain);
diff --git a/src/test/com/jogamp/opengl/test/android/MovieSimpleActivity0.java b/src/test/com/jogamp/opengl/test/android/MovieSimpleActivity0.java
index bcff3d5bd..7a92360fb 100644
--- a/src/test/com/jogamp/opengl/test/android/MovieSimpleActivity0.java
+++ b/src/test/com/jogamp/opengl/test/android/MovieSimpleActivity0.java
@@ -45,6 +45,7 @@ import com.jogamp.newt.opengl.GLWindow;
import com.jogamp.opengl.test.junit.jogl.demos.es2.av.MovieSimple;
import com.jogamp.opengl.util.Animator;
+import com.jogamp.opengl.util.av.GLMediaPlayer;
import android.os.Bundle;
import android.util.Log;
@@ -84,7 +85,7 @@ public class MovieSimpleActivity0 extends NewtBaseActivity {
final Animator animator = new Animator();
// Main
- final MovieSimple demoMain = new MovieSimple(urlConnection0);
+ final MovieSimple demoMain = new MovieSimple(urlConnection0, GLMediaPlayer.STREAM_ID_AUTO, GLMediaPlayer.STREAM_ID_AUTO);
demoMain.setScaleOrig(true);
final GLWindow glWindowMain = GLWindow.create(scrn, capsMain);
glWindowMain.setFullscreen(true);
diff --git a/src/test/com/jogamp/opengl/test/android/MovieSimpleActivity1.java b/src/test/com/jogamp/opengl/test/android/MovieSimpleActivity1.java
index cb0fd0720..d0fb41828 100644
--- a/src/test/com/jogamp/opengl/test/android/MovieSimpleActivity1.java
+++ b/src/test/com/jogamp/opengl/test/android/MovieSimpleActivity1.java
@@ -111,7 +111,7 @@ public class MovieSimpleActivity1 extends NewtBaseActivity {
final Animator animator = new Animator();
// Main
- final MovieSimple demoMain = new MovieSimple(urlConnection0);
+ final MovieSimple demoMain = new MovieSimple(urlConnection0, GLMediaPlayer.STREAM_ID_AUTO, GLMediaPlayer.STREAM_ID_AUTO);
if(mPlayerHUD) {
demoMain.setEffects(MovieSimple.EFFECT_GRADIENT_BOTTOM2TOP);
demoMain.setTransparency(0.9f);
@@ -154,7 +154,7 @@ public class MovieSimpleActivity1 extends NewtBaseActivity {
glWindowHUD.addGLEventListener(new MovieSimple(sharedPlayer));
} else {
try {
- glWindowHUD.addGLEventListener(new MovieSimple(urlConnection1));
+ glWindowHUD.addGLEventListener(new MovieSimple(urlConnection1, GLMediaPlayer.STREAM_ID_AUTO, GLMediaPlayer.STREAM_ID_AUTO));
} catch (IOException e) {
e.printStackTrace();
}
diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/TextureSequenceCubeES2.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/TextureSequenceCubeES2.java
index adccecba0..556d17992 100644
--- a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/TextureSequenceCubeES2.java
+++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/TextureSequenceCubeES2.java
@@ -332,22 +332,32 @@ public class TextureSequenceCubeES2 implements GLEventListener {
}
+ @Override
public void dispose(GLAutoDrawable drawable) {
GL2ES2 gl = drawable.getGL().getGL2ES2();
texSeq = null;
pmvMatrixUniform = null;
- pmvMatrix.destroy();
- pmvMatrix=null;
- st.destroy(gl);
- st=null;
+ if( null != pmvMatrix ) {
+ pmvMatrix.destroy();
+ pmvMatrix=null;
+ }
+ if( null != st ) {
+ st.destroy(gl);
+ st=null;
+ }
}
+ @Override
public void display(GLAutoDrawable drawable) {
GL2ES2 gl = drawable.getGL().getGL2ES2();
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
-
+
+ if( null == st ) {
+ return;
+ }
+
st.useProgram(gl, true);
pmvMatrix.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
@@ -379,9 +389,6 @@ public class TextureSequenceCubeES2 implements GLEventListener {
st.useProgram(gl, false);
}
- public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {
- }
-
static final float[] light_position = { -50.f, 50.f, 50.f, 0.f };
static final float[] light_ambient = { 0.125f, 0.125f, 0.125f, 1.f };
static final float[] light_diffuse = { 1.0f, 1.0f, 1.0f, 1.f };
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 3f979e16f..fbbd77260 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
@@ -57,6 +57,7 @@ import com.jogamp.newt.opengl.GLWindow;
import com.jogamp.opengl.JoglVersion;
import com.jogamp.opengl.test.junit.jogl.demos.es2.TextureSequenceCubeES2;
import com.jogamp.opengl.test.junit.util.MiscUtils;
+import com.jogamp.opengl.test.junit.util.UITestCase;
import com.jogamp.opengl.util.Animator;
import com.jogamp.opengl.util.av.GLMediaPlayer;
import com.jogamp.opengl.util.av.GLMediaPlayer.GLMediaEventListener;
@@ -67,20 +68,25 @@ public class MovieCube implements GLEventListener, GLMediaEventListener {
static boolean waitForKey = false;
int textureCount = 3; // default - threaded
final URLConnection stream;
+ final int vid, aid;
final float zoom0, rotx, roty;
TextureSequenceCubeES2 cube=null;
GLMediaPlayer mPlayer=null;
public MovieCube() throws IOException {
this(new URL("http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4").openConnection(),
- -2.3f, 0f, 0f);
+ GLMediaPlayer.STREAM_ID_AUTO, GLMediaPlayer.STREAM_ID_AUTO, -2.3f, 0f, 0f);
}
- public MovieCube(URLConnection stream, float zoom0, float rotx, float roty) throws IOException {
+ public MovieCube(URLConnection stream, int vid, int aid, float zoom0, float rotx, float roty) throws IOException {
this.stream = stream;
this.zoom0 = zoom0;
this.rotx = rotx;
this.roty = roty;
+ this.vid = vid;
+ this.aid = aid;
+ mPlayer = GLMediaPlayerFactory.createDefault();
+ mPlayer.addEventListener(this);
}
public void setTextureCount(int v) {
@@ -89,11 +95,11 @@ public class MovieCube implements GLEventListener, GLMediaEventListener {
private final KeyListener keyAction = new KeyAdapter() {
public void keyReleased(KeyEvent e) {
- if( !e.isPrintableKey() || e.isAutoRepeat() ) {
+ if( e.isAutoRepeat() ) {
return;
}
System.err.println("MC "+e);
- int pts0 = mPlayer.getCurrentPosition();
+ int pts0 = mPlayer.getVideoPTS();
int pts1 = 0;
switch(e.getKeyCode()) {
case KeyEvent.VK_3:
@@ -107,21 +113,37 @@ public class MovieCube implements GLEventListener, GLMediaEventListener {
case KeyEvent.VK_ESCAPE:
case KeyEvent.VK_DELETE:
case KeyEvent.VK_BACK_SPACE: {
- mPlayer.seek(0);
mPlayer.setPlaySpeed(1.0f);
- mPlayer.start();
+ mPlayer.seek(0);
+ mPlayer.play();
break;
}
case KeyEvent.VK_SPACE: {
if(GLMediaPlayer.State.Paused == mPlayer.getState()) {
- mPlayer.start();
+ mPlayer.play();
} else {
mPlayer.pause();
}
break;
}
- case KeyEvent.VK_S: mPlayer.setPlaySpeed(mPlayer.getPlaySpeed()/2.0f); break;
- case KeyEvent.VK_F: mPlayer.setPlaySpeed(mPlayer.getPlaySpeed()*2.0f); break;
+ case KeyEvent.VK_SUBTRACT: {
+ float playSpeed = mPlayer.getPlaySpeed();
+ if( e.isShiftDown() ) {
+ playSpeed /= 2.0f;
+ } else {
+ playSpeed -= 0.1f;
+ }
+ mPlayer.setPlaySpeed(playSpeed);
+ } break;
+ case KeyEvent.VK_ADD: {
+ float playSpeed = mPlayer.getPlaySpeed();
+ if( e.isShiftDown() ) {
+ playSpeed *= 2.0f;
+ } else {
+ playSpeed += 0.1f;
+ }
+ mPlayer.setPlaySpeed(playSpeed);
+ } break;
}
if( 0 != pts1 ) {
@@ -140,25 +162,18 @@ public class MovieCube implements GLEventListener, GLMediaEventListener {
// System.out.println("newFrameAvailable: "+mp+", when "+when);
}
+ @Override
public void init(GLAutoDrawable drawable) {
GL2ES2 gl = drawable.getGL().getGL2ES2();
System.err.println(JoglVersion.getGLInfo(gl, null));
- mPlayer = GLMediaPlayerFactory.createDefault();
- mPlayer.addEventListener(this);
cube = new TextureSequenceCubeES2(mPlayer, false, zoom0, rotx, roty);
if(waitForKey) {
- BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
- System.err.println("Press enter to continue");
- try {
- System.err.println(stdin.readLine());
- } catch (IOException e) { }
+ UITestCase.waitForKey("Init>");
}
try {
- System.out.println("p0 "+mPlayer);
- mPlayer.initGLStream(gl, textureCount, stream);
- System.out.println("p1 "+mPlayer);
+ mPlayer.initGLStream(gl, textureCount, stream, vid, aid);
} catch (Exception e) {
e.printStackTrace();
if(null != mPlayer) {
@@ -169,7 +184,7 @@ public class MovieCube implements GLEventListener, GLMediaEventListener {
}
cube.init(drawable);
- mPlayer.start();
+ mPlayer.play();
boolean added;
final Object upstreamWidget = drawable.getUpstreamWidget();
@@ -181,30 +196,38 @@ public class MovieCube implements GLEventListener, GLMediaEventListener {
System.err.println("MC.init: kl-added "+added+", "+drawable.getClass().getName());
}
+ @Override
public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
if(null == mPlayer) { return; }
cube.reshape(drawable, x, y, width, height);
}
+ @Override
public void dispose(GLAutoDrawable drawable) {
System.err.println(Thread.currentThread()+" MovieCube.dispose ... ");
if(null == mPlayer) { return; }
- mPlayer.stop();
- GL2ES2 gl = drawable.getGL().getGL2ES2();
+ final GL2ES2 gl = drawable.getGL().getGL2ES2();
mPlayer.destroy(gl);
mPlayer=null;
cube.dispose(drawable);
cube=null;
}
+ long lastPerfPos = 0;
+
+ @Override
public void display(GLAutoDrawable drawable) {
if(null == mPlayer) { return; }
+
+ final long currentPos = System.currentTimeMillis();
+ if( currentPos - lastPerfPos > 2000 ) {
+ System.err.println( mPlayer.getPerfString() );
+ lastPerfPos = currentPos;
+ }
+
cube.display(drawable);
}
- public void displayChanged(javax.media.opengl.GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {
- }
-
public static void main(String[] args) throws MalformedURLException, IOException, InterruptedException {
int width = 510;
int height = 300;
@@ -214,40 +237,56 @@ public class MovieCube implements GLEventListener, GLMediaEventListener {
boolean forceES3 = false;
boolean forceGL3 = false;
boolean forceGLDef = false;
+ int vid = GLMediaPlayer.STREAM_ID_AUTO;
+ int aid = GLMediaPlayer.STREAM_ID_AUTO;
+ final boolean origSize;
- String url_s="http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4";
- for(int i=0; i<args.length; i++) {
- if(args[i].equals("-width")) {
- i++;
- width = MiscUtils.atoi(args[i], width);
- } else if(args[i].equals("-height")) {
- i++;
- height = MiscUtils.atoi(args[i], height);
- } else if(args[i].equals("-textureCount")) {
- i++;
- textureCount = MiscUtils.atoi(args[i], textureCount);
- } else if(args[i].equals("-url")) {
- i++;
- url_s = args[i];
- } else if(args[i].equals("-es2")) {
- forceES2 = true;
- } else if(args[i].equals("-es3")) {
- forceES3 = true;
- } else if(args[i].equals("-gl3")) {
- forceGL3 = true;
- } else if(args[i].equals("-gldef")) {
- forceGLDef = true;
- } else if(args[i].equals("-wait")) {
- waitForKey = true;
+ String url_s="http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4";
+ {
+ boolean _origSize = false;
+ for(int i=0; i<args.length; i++) {
+ if(args[i].equals("-vid")) {
+ i++;
+ vid = MiscUtils.atoi(args[i], vid);
+ } else if(args[i].equals("-aid")) {
+ i++;
+ aid = MiscUtils.atoi(args[i], aid);
+ } else if(args[i].equals("-width")) {
+ i++;
+ width = MiscUtils.atoi(args[i], width);
+ } else if(args[i].equals("-height")) {
+ i++;
+ height = MiscUtils.atoi(args[i], height);
+ } else if(args[i].equals("-osize")) {
+ _origSize = true;
+ } else if(args[i].equals("-textureCount")) {
+ i++;
+ textureCount = MiscUtils.atoi(args[i], textureCount);
+ } else if(args[i].equals("-url")) {
+ i++;
+ url_s = args[i];
+ } else if(args[i].equals("-es2")) {
+ forceES2 = true;
+ } else if(args[i].equals("-es3")) {
+ forceES3 = true;
+ } else if(args[i].equals("-gl3")) {
+ forceGL3 = true;
+ } else if(args[i].equals("-gldef")) {
+ forceGLDef = true;
+ } else if(args[i].equals("-wait")) {
+ waitForKey = true;
+ }
}
+ origSize = _origSize;
}
+ System.err.println("vid "+vid+", aid "+aid);
System.err.println("textureCount "+textureCount);
System.err.println("forceES2 "+forceES2);
System.err.println("forceES3 "+forceES3);
System.err.println("forceGL3 "+forceGL3);
System.err.println("forceGLDef "+forceGLDef);
- final MovieCube mc = new MovieCube(new URL(url_s).openConnection(), -2.3f, 0f, 0f);
+ final MovieCube mc = new MovieCube(new URL(url_s).openConnection(), vid, aid, -2.3f, 0f, 0f);
final GLProfile glp;
if(forceGLDef) {
@@ -265,18 +304,30 @@ public class MovieCube implements GLEventListener, GLMediaEventListener {
final GLWindow window = GLWindow.create(new GLCapabilities(glp));
// Size OpenGL to Video Surface
window.setSize(width, height);
- window.setFullscreen(false);
- window.setSize(width, height);
window.addGLEventListener(mc);
+
+ mc.mPlayer.addEventListener(new GLMediaEventListener() {
+ @Override
+ public void newFrameAvailable(GLMediaPlayer ts, TextureFrame newFrame, long when) {
+ }
+
+ @Override
+ public void attributesChanges(final GLMediaPlayer mp, int event_mask, long when) {
+ if( 0 != ( GLMediaEventListener.EVENT_CHANGE_SIZE & event_mask ) && origSize ) {
+ window.setSize(mp.getWidth(), mp.getHeight());
+ }
+ }
+ });
+
final Animator anim = new Animator(window);
window.addWindowListener(new WindowAdapter() {
public void windowDestroyed(WindowEvent e) {
anim.stop();
}
});
+ window.setVisible(true);
anim.setUpdateFPSFrames(60, System.err);
anim.start();
- window.setVisible(true);
}
}
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 90c73661a..f5490d19a 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
@@ -46,6 +46,9 @@ import javax.media.opengl.GLUniformData;
import javax.media.opengl.fixedfunc.GLMatrixFunc;
import com.jogamp.newt.Window;
+import com.jogamp.newt.event.KeyAdapter;
+import com.jogamp.newt.event.KeyEvent;
+import com.jogamp.newt.event.KeyListener;
import com.jogamp.newt.event.MouseAdapter;
import com.jogamp.newt.event.MouseEvent;
import com.jogamp.newt.event.MouseListener;
@@ -54,6 +57,7 @@ import com.jogamp.newt.event.WindowEvent;
import com.jogamp.newt.opengl.GLWindow;
import com.jogamp.opengl.JoglVersion;
import com.jogamp.opengl.test.junit.util.MiscUtils;
+import com.jogamp.opengl.test.junit.util.UITestCase;
import com.jogamp.opengl.util.Animator;
import com.jogamp.opengl.util.GLArrayDataServer;
import com.jogamp.opengl.util.PMVMatrix;
@@ -69,6 +73,7 @@ import com.jogamp.opengl.util.texture.TextureSequence;
import com.jogamp.opengl.util.texture.TextureSequence.TextureFrame;
public class MovieSimple implements GLEventListener, GLMediaEventListener {
+ static boolean waitForKey = false;
private int winWidth, winHeight;
int textureCount = 3; // default - threaded
private int prevMouseX; // , prevMouseY;
@@ -98,7 +103,8 @@ public class MovieSimple implements GLEventListener, GLMediaEventListener {
}
GLMediaPlayer mPlayer;
- URLConnection stream = null;
+ final URLConnection stream;
+ final int vid, aid;
boolean mPlayerExternal;
boolean mPlayerShared;
boolean mPlayerScaleOrig;
@@ -110,7 +116,7 @@ public class MovieSimple implements GLEventListener, GLMediaEventListener {
if(GLMediaPlayer.State.Playing == mPlayer.getState()) {
mPlayer.pause();
} else {
- mPlayer.start();
+ mPlayer.play();
}
}
}
@@ -131,9 +137,9 @@ public class MovieSimple implements GLEventListener, GLMediaEventListener {
if(y>winHeight/2) {
final float dp = (float)(x-prevMouseX)/(float)winWidth;
- mPlayer.seek(mPlayer.getCurrentPosition() + (int) (mPlayer.getDuration() * dp));
+ mPlayer.seek(mPlayer.getVideoPTS() + (int) (mPlayer.getDuration() * dp));
} else {
- mPlayer.start();
+ mPlayer.play();
rotate = 1;
zoom = zoom1;
}
@@ -149,13 +155,74 @@ public class MovieSimple implements GLEventListener, GLMediaEventListener {
}
};
- public MovieSimple(URLConnection stream) throws IOException {
+ private final KeyListener keyAction = new KeyAdapter() {
+ public void keyReleased(KeyEvent e) {
+ if( e.isAutoRepeat() ) {
+ return;
+ }
+ System.err.println("MC "+e);
+ int pts0 = mPlayer.getVideoPTS();
+ int pts1 = 0;
+ switch(e.getKeyCode()) {
+ case KeyEvent.VK_3:
+ case KeyEvent.VK_RIGHT: pts1 = pts0 + 1000; break;
+ case KeyEvent.VK_4:
+ case KeyEvent.VK_UP: pts1 = pts0 + 10000; break;
+ case KeyEvent.VK_2:
+ case KeyEvent.VK_LEFT: pts1 = pts0 - 1000; break;
+ case KeyEvent.VK_1:
+ case KeyEvent.VK_DOWN: pts1 = pts0 - 10000; break;
+ case KeyEvent.VK_ESCAPE:
+ case KeyEvent.VK_DELETE:
+ case KeyEvent.VK_BACK_SPACE: {
+ mPlayer.setPlaySpeed(1.0f);
+ mPlayer.seek(0);
+ mPlayer.play();
+ break;
+ }
+ case KeyEvent.VK_SPACE: {
+ if(GLMediaPlayer.State.Paused == mPlayer.getState()) {
+ mPlayer.play();
+ } else {
+ mPlayer.pause();
+ }
+ break;
+ }
+ case KeyEvent.VK_SUBTRACT: {
+ float playSpeed = mPlayer.getPlaySpeed();
+ if( e.isShiftDown() ) {
+ playSpeed /= 2.0f;
+ } else {
+ playSpeed -= 0.1f;
+ }
+ mPlayer.setPlaySpeed(playSpeed);
+ } break;
+ case KeyEvent.VK_ADD: {
+ float playSpeed = mPlayer.getPlaySpeed();
+ if( e.isShiftDown() ) {
+ playSpeed *= 2.0f;
+ } else {
+ playSpeed += 0.1f;
+ }
+ mPlayer.setPlaySpeed(playSpeed);
+ } break;
+ }
+
+ if( 0 != pts1 ) {
+ mPlayer.seek(pts1);
+ }
+ }
+ };
+
+ public MovieSimple(URLConnection stream, int vid, int aid) throws IOException {
mPlayerScaleOrig = false;
mPlayerShared = false;
mPlayerExternal = false;
mPlayer = GLMediaPlayerFactory.createDefault();
mPlayer.addEventListener(this);
this.stream = stream;
+ this.vid = vid;
+ this.aid = aid;
System.out.println("pC.1 "+mPlayer);
}
@@ -166,6 +233,8 @@ public class MovieSimple implements GLEventListener, GLMediaEventListener {
mPlayer = sharedMediaPlayer;
mPlayer.addEventListener(this);
this.stream = null;
+ this.vid = sharedMediaPlayer.getVID();
+ this.aid = sharedMediaPlayer.getAID();
System.out.println("pC.2 shared "+mPlayerShared+", "+mPlayer);
}
@@ -188,20 +257,13 @@ public class MovieSimple implements GLEventListener, GLMediaEventListener {
// System.out.println("newFrameAvailable: "+mp+", when "+when);
}
- public void start() {
+ public void play() {
if(null!=mPlayer) {
- mPlayer.start();
+ mPlayer.play();
System.out.println("pStart "+mPlayer);
}
}
- public void stop() {
- if(null!=mPlayer) {
- mPlayer.stop();
- System.out.println("pStop "+mPlayer);
- }
- }
-
ShaderState st;
PMVMatrix pmvMatrix;
GLUniformData pmvMatrixUniform;
@@ -242,6 +304,7 @@ public class MovieSimple implements GLEventListener, GLMediaEventListener {
st.attachShaderProgram(gl, sp, false);
}
+ @Override
public void init(GLAutoDrawable drawable) {
zoom0 = orthoProjection ? 0f : -2.5f;
zoom1 = orthoProjection ? 0f : -5f;
@@ -252,12 +315,15 @@ public class MovieSimple implements GLEventListener, GLMediaEventListener {
System.err.println("Alpha: "+alpha+", opaque "+drawable.getChosenGLCapabilities().isBackgroundOpaque()+
", "+drawable.getClass().getName()+", "+drawable);
+ if(waitForKey) {
+ UITestCase.waitForKey("Init>");
+ }
final Texture tex;
boolean useExternalTexture = false;
try {
System.out.println("p0 "+mPlayer+", shared "+mPlayerShared);
if(!mPlayerShared) {
- mPlayer.initGLStream(gl, textureCount, stream);
+ mPlayer.initGLStream(gl, textureCount, stream, vid, aid);
}
tex = mPlayer.getLastTexture().getTexture();
System.out.println("p1 "+mPlayer+", shared "+mPlayerShared);
@@ -392,7 +458,7 @@ public class MovieSimple implements GLEventListener, GLMediaEventListener {
System.out.println(st);
if(null!=mPlayer) {
- start();
+ play();
System.out.println("p2 "+mPlayer);
}
@@ -402,11 +468,13 @@ public class MovieSimple implements GLEventListener, GLMediaEventListener {
if (upstreamWidget instanceof Window) {
final Window window = (Window) upstreamWidget;
window.addMouseListener(mouseAction);
+ window.addKeyListener(keyAction);
winWidth = window.getWidth();
winHeight = window.getHeight();
}
}
+ @Override
public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
if(null == mPlayer) { return; }
winWidth = width;
@@ -442,34 +510,51 @@ public class MovieSimple implements GLEventListener, GLMediaEventListener {
pmvMatrix.glTranslatef(0, 0, zoom0);
}
+ @Override
public void dispose(GLAutoDrawable drawable) {
if(null == mPlayer) { return; }
- stop();
- System.out.println("pD.1 "+mPlayer);
-
+ System.out.println("pD.1 "+mPlayer);
GL2ES2 gl = drawable.getGL().getGL2ES2();
-
- mPlayer.removeEventListener(this);
- if(!mPlayerExternal) {
- mPlayer.destroy(gl);
+ if( null != mPlayer ) {
+ mPlayer.removeEventListener(this);
+ if(!mPlayerExternal) {
+ mPlayer.destroy(gl);
+ }
}
System.out.println("pD.X "+mPlayer);
mPlayer=null;
pmvMatrixUniform = null;
- pmvMatrix.destroy();
- pmvMatrix=null;
- st.destroy(gl);
- st=null;
+ if(null != pmvMatrix) {
+ pmvMatrix.destroy();
+ pmvMatrix=null;
+ }
+ if(null != st) {
+ st.destroy(gl);
+ st=null;
+ }
}
+ long lastPerfPos = 0;
+
+ @Override
public void display(GLAutoDrawable drawable) {
if(null == mPlayer) { return; }
+ final long currentPos = System.currentTimeMillis();
+ if( currentPos - lastPerfPos > 2000 ) {
+ System.err.println( mPlayer.getPerfString() );
+ lastPerfPos = currentPos;
+ }
+
GL2ES2 gl = drawable.getGL().getGL2ES2();
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
+ if(null == st) {
+ return;
+ }
+
st.useProgram(gl, true);
pmvMatrix.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
@@ -506,9 +591,6 @@ public class MovieSimple implements GLEventListener, GLMediaEventListener {
st.useProgram(gl, false);
}
- public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {
- }
-
public static void main(String[] args) throws IOException, MalformedURLException {
int width = 640;
int height = 600;
@@ -520,42 +602,60 @@ public class MovieSimple implements GLEventListener, GLMediaEventListener {
boolean forceES3 = false;
boolean forceGL3 = false;
boolean forceGLDef = false;
+ int vid = GLMediaPlayer.STREAM_ID_AUTO;
+ int aid = GLMediaPlayer.STREAM_ID_AUTO;
+ final boolean origSize;
- String url_s="http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4";
- for(int i=0; i<args.length; i++) {
- if(args[i].equals("-width")) {
- i++;
- width = MiscUtils.atoi(args[i], width);
- } else if(args[i].equals("-height")) {
- i++;
- height = MiscUtils.atoi(args[i], height);
- } else if(args[i].equals("-textureCount")) {
- i++;
- textureCount = MiscUtils.atoi(args[i], textureCount);
- } else if(args[i].equals("-es2")) {
- forceES2 = true;
- } else if(args[i].equals("-es3")) {
- forceES3 = true;
- } else if(args[i].equals("-gl3")) {
- forceGL3 = true;
- } else if(args[i].equals("-gldef")) {
- forceGLDef = true;
- } else if(args[i].equals("-projection")) {
- ortho=false;
- } else if(args[i].equals("-zoom")) {
- zoom=true;
- } else if(args[i].equals("-url")) {
- i++;
- url_s = args[i];
+ String url_s="http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4";
+ {
+ boolean _origSize = false;
+ for(int i=0; i<args.length; i++) {
+ if(args[i].equals("-vid")) {
+ i++;
+ vid = MiscUtils.atoi(args[i], vid);
+ } else if(args[i].equals("-aid")) {
+ i++;
+ aid = MiscUtils.atoi(args[i], aid);
+ } else if(args[i].equals("-width")) {
+ i++;
+ width = MiscUtils.atoi(args[i], width);
+ } else if(args[i].equals("-height")) {
+ i++;
+ height = MiscUtils.atoi(args[i], height);
+ } else if(args[i].equals("-osize")) {
+ _origSize = true;
+ } else if(args[i].equals("-textureCount")) {
+ i++;
+ textureCount = MiscUtils.atoi(args[i], textureCount);
+ } else if(args[i].equals("-es2")) {
+ forceES2 = true;
+ } else if(args[i].equals("-es3")) {
+ forceES3 = true;
+ } else if(args[i].equals("-gl3")) {
+ forceGL3 = true;
+ } else if(args[i].equals("-gldef")) {
+ forceGLDef = true;
+ } else if(args[i].equals("-projection")) {
+ ortho=false;
+ } else if(args[i].equals("-zoom")) {
+ zoom=true;
+ } else if(args[i].equals("-url")) {
+ i++;
+ url_s = args[i];
+ } else if(args[i].equals("-wait")) {
+ waitForKey = true;
+ }
}
+ origSize = _origSize;
}
+ System.err.println("vid "+vid+", aid "+aid);
System.err.println("textureCount "+textureCount);
System.err.println("forceES2 "+forceES2);
System.err.println("forceES3 "+forceES3);
System.err.println("forceGL3 "+forceGL3);
System.err.println("forceGLDef "+forceGLDef);
- final MovieSimple ms = new MovieSimple(new URL(url_s).openConnection());
+ final MovieSimple ms = new MovieSimple(new URL(url_s).openConnection(), vid, aid);
ms.setTextureCount(textureCount);
ms.setScaleOrig(!zoom);
ms.setOrthoProjection(ortho);
@@ -575,9 +675,21 @@ public class MovieSimple implements GLEventListener, GLMediaEventListener {
}
System.err.println("GLProfile: "+glp);
GLCapabilities caps = new GLCapabilities(glp);
- GLWindow window = GLWindow.create(caps);
+ final GLWindow window = GLWindow.create(caps);
window.addGLEventListener(ms);
+ ms.mPlayer.addEventListener(new GLMediaEventListener() {
+ @Override
+ public void newFrameAvailable(GLMediaPlayer ts, TextureFrame newFrame, long when) {
+ }
+
+ @Override
+ public void attributesChanges(final GLMediaPlayer mp, int event_mask, long when) {
+ if( 0 != ( GLMediaEventListener.EVENT_CHANGE_SIZE & event_mask ) && origSize ) {
+ window.setSize(mp.getWidth(), mp.getHeight());
+ }
+ }
+ });
window.setSize(width, height);
window.setVisible(true);