aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSven Göthel <[email protected]>2024-01-28 06:42:50 +0100
committerSven Göthel <[email protected]>2024-01-28 06:42:50 +0100
commit6c67d73dc6b9e49bdd406902e533be91db1a3b0a (patch)
tree0ab66480a4ba83dd1620227ed6b2f4aa45930593
parent62a1f18e98df6783d487f7dfbbc83026b04a19b8 (diff)
GLMediaPlayer/FFMPEGMediaPlayer: Add working subtitle (text + ass/saa) support via FFMpeg
TODO: - We may want to refine subtitle PTS handling - We may want to support bitmapped subtitles
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/av/ASSEventLine.java135
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/av/ASSEventListener.java35
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java5
-rw-r--r--src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java22
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java34
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java10
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java14
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java14
-rw-r--r--src/jogl/native/libav/ffmpeg_impl_template.c153
-rw-r--r--src/jogl/native/libav/ffmpeg_static.c8
-rw-r--r--src/jogl/native/libav/ffmpeg_static.h2
-rw-r--r--src/jogl/native/libav/ffmpeg_tool.h5
12 files changed, 401 insertions, 36 deletions
diff --git a/src/jogl/classes/com/jogamp/opengl/util/av/ASSEventLine.java b/src/jogl/classes/com/jogamp/opengl/util/av/ASSEventLine.java
new file mode 100644
index 000000000..6389df773
--- /dev/null
+++ b/src/jogl/classes/com/jogamp/opengl/util/av/ASSEventLine.java
@@ -0,0 +1,135 @@
+/**
+ * Copyright 2024 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package com.jogamp.opengl.util.av;
+
+/**
+ * ASS/SAA Event Line of subtitles
+ * <p>
+ * See http://www.tcax.org/docs/ass-specs.htm
+ * </p>
+ */
+public class ASSEventLine {
+ public enum Format {
+ UNKNOWN,
+ /** FFMpeg output w/o start, end:
+ * <pre>
+ 0 1 2 3 4 5 6 7 8
+ Seq, Layer, Style, Name, MarginL, MarginR, MarginV, Effect, TEXT
+ * </pre>
+ */
+ FFMPEG,
+ /** Just the plain text part */
+ TEXT
+ };
+ public final Format source;
+ public final int seqnr;
+ public final int pts_start;
+ public final int pts_end;
+ public final int layer;
+ public final String style;
+ public final String name;
+ /** Actual subtitle text */
+ public final String text;
+ /** Number of lines of {@link #text}, i.e. occurrence of {@code \n} + 1. */
+ public final int lines;
+
+ /**
+ * ASS/SAA Event Line ctor
+ * @param fmt input format of {@code ass}, currently only {@link ASSEventLine.Format#FFMPEG} and {@link ASSEventLine.Format#TEXT} is supported
+ * @param ass ASS/SAA compatible event line according to {@code fmt}
+ * @param pts_start pts start in ms, provided for {@link ASSEventLine.Format#FFMPEG} and {@link ASSEventLine.Format#TEXT}
+ * @param pts_end pts end in ms, provided for {@link ASSEventLine.Format#FFMPEG} and {@link ASSEventLine.Format#TEXT}
+ */
+ public ASSEventLine(final Format fmt, final String ass, final int pts_start, final int pts_end) {
+ this.source = fmt;
+ int seqnr = 0;
+ int layer = 0;
+ String style = "Default";
+ String name = "";
+ String text = "";
+ if( Format.FFMPEG == fmt ) {
+ final int len = null != ass ? ass.length() : 0;
+ int part = 0;
+ for(int i=0; 9 > part && len > i; ) {
+ if( 8 == part ) {
+ text = ass.substring(i);
+ } else {
+ final int j = ass.indexOf(',', i);
+ if( 0 > j ) {
+ break;
+ }
+ final String v = ass.substring(i, j);
+ switch(part) {
+ case 0:
+ seqnr = Integer.valueOf(v);
+ break;
+ case 1:
+ layer = Integer.valueOf(v);
+ break;
+ case 2:
+ style = v;
+ break;
+ case 3:
+ name = v;
+ break;
+ }
+ i = j + 1;
+ }
+ ++part;
+ }
+ } else if( Format.TEXT == fmt ) {
+ text = ass;
+ }
+ this.seqnr = seqnr;
+ this.pts_start = pts_start;
+ this.pts_end = pts_end;
+ this.layer = layer;
+ this.style = style;
+ this.name = name;
+ this.text = text.replace("\\N", "\n");
+ {
+ final int len = this.text.length();
+ int lc = 1;
+ for(int i=0; len > i; ) {
+ final int j = this.text.indexOf("\n", i);
+ if( 0 > j ) {
+ break;
+ }
+ ++lc;
+ i = j + 1;
+ }
+ this.lines = lc;
+ }
+ }
+ public int getDuration() { return pts_end - pts_start + 1; }
+
+ @Override
+ public String toString() {
+ return "ASS["+source+", #"+seqnr+", l_"+layer+", ["+pts_start+".."+pts_end+"] "+getDuration()+" ms, style "+style+", name '"+name+"': '"+text+"' ("+lines+")]";
+ }
+}
diff --git a/src/jogl/classes/com/jogamp/opengl/util/av/ASSEventListener.java b/src/jogl/classes/com/jogamp/opengl/util/av/ASSEventListener.java
new file mode 100644
index 000000000..3baf6b69c
--- /dev/null
+++ b/src/jogl/classes/com/jogamp/opengl/util/av/ASSEventListener.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright 2024 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package com.jogamp.opengl.util.av;
+
+/**
+ * {@link ASSEventLine} Listener
+ */
+public interface ASSEventListener {
+ void run(ASSEventLine e);
+}
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 646280f08..698504205 100644
--- a/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java
+++ b/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java
@@ -899,6 +899,11 @@ public interface GLMediaPlayer extends TextureSequence {
/** Return all {@link GLMediaEventListener} of this player. */
public GLMediaEventListener[] getEventListeners();
+ /** Sets the {@link ASSEventListener} for this player. */
+ public void setASSEventListener(ASSEventListener l);
+ /** Returns the {@link #setASSEventListener(ASSEventListener)} of this player. */
+ public ASSEventListener getASSEventListener();
+
/**
* Returns the attached user object for the given name.
*/
diff --git a/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java b/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java
index 3585f7ab2..c4d1ee78f 100644
--- a/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java
+++ b/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java
@@ -281,7 +281,7 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl {
}
@Override
- protected final void initStreamImpl(final int vid, final int aid, int sid) throws IOException {
+ protected final void initStreamImpl(final int vid, final int aid, final int sid) throws IOException {
if( null == getUri() ) {
return;
}
@@ -336,10 +336,12 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl {
r_alangs = new String[] { "n/a" };
}
final String icodec = "android";
- updateAttributes(null, new int[] { 0 }, new String[] { "und" },
- 0 /* fake */, r_aids, r_alangs,
- r_aid, new int[0], new String[0],
- GLMediaPlayer.STREAM_ID_NONE, mp.getVideoWidth(), mp.getVideoHeight(), 0, 0, 0, 0f, 0, 0, mp.getDuration(), icodec, icodec);
+ updateAttributes(null,
+ new int[] { 0 }, new String[] { "und" }, 0 /* fake */,
+ r_aids, r_alangs, r_aid,
+ new int[0], new String[0], GLMediaPlayer.STREAM_ID_NONE,
+ mp.getVideoWidth(), mp.getVideoHeight(), 0, 0, 0, 0f, 0, 0, mp.getDuration(),
+ icodec, icodec, null);
/**
mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
@@ -372,10 +374,12 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl {
}
}
}
- updateAttributes(null, new int[]{0}, new String[] { "und" },
- 0 /* fake */, new int[0], new String[0],
- GLMediaPlayer.STREAM_ID_NONE, new int[0], new String[0],
- GLMediaPlayer.STREAM_ID_NONE, size.width, size.height, 0, 0, 0, fpsRange[1]/1000f, 0, 0, 0, icodec, icodec);
+ updateAttributes(null,
+ new int[]{0}, new String[] { "und" }, 0 /* fake */,
+ new int[0], new String[0], GLMediaPlayer.STREAM_ID_NONE,
+ new int[0], new String[0], GLMediaPlayer.STREAM_ID_NONE,
+ size.width, size.height, 0, 0, 0, fpsRange[1]/1000f, 0, 0, 0,
+ icodec, icodec, null);
}
}
private static String camSz2Str(final Camera.Size csize) {
diff --git a/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java b/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java
index 5df858b2d..a36213a01 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java
@@ -66,6 +66,7 @@ import com.jogamp.common.util.TSPrinter;
import com.jogamp.common.util.WorkerThread;
import com.jogamp.math.FloatUtil;
import com.jogamp.opengl.GLExtensions;
+import com.jogamp.opengl.util.av.ASSEventListener;
import com.jogamp.opengl.util.av.GLMediaPlayer;
import com.jogamp.opengl.util.glsl.ShaderCode;
import com.jogamp.opengl.util.texture.Texture;
@@ -175,6 +176,8 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
private String acodec = unknown;
/** Shall be set by the {@link #initStreamImpl(int, int, int)} method implementation. */
private String vcodec = unknown;
+ /** Shall be set by the {@link #initStreamImpl(int, int, int)} method implementation. */
+ private String scodec = unknown;
private volatile int decodedFrameCount = 0;
private int presentedFrameCount = 0;
@@ -196,6 +199,8 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
protected AudioSink audioSink = null;
protected boolean audioSinkPlaySpeedSet = false;
+ protected volatile ASSEventListener assEventListener = null;
+
/** AV System Clock Reference (SCR) */
private final PTS av_scr = new PTS( () -> { return State.Playing == state ? playSpeed : 0f; } );
private final PTS av_scr_cpy = new PTS( av_scr );
@@ -952,11 +957,12 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
final float _fps = 24f;
final int _duration = 10*60*1000; // msec
final int _totalFrames = (int) ( (_duration/1000)*_fps );
- updateAttributes("test", new int[0], new String[0],
- GLMediaPlayer.STREAM_ID_NONE, new int[0], new String[0], // audio
- GLMediaPlayer.STREAM_ID_NONE, new int[0], new String[0], // subs
- GLMediaPlayer.STREAM_ID_NONE,
- TestTexture.singleton.getWidth(), TestTexture.singleton.getHeight(), 0, 0, 0, _fps, _totalFrames, 0, _duration, "png-static", null);
+ updateAttributes("test",
+ new int[0], new String[0], GLMediaPlayer.STREAM_ID_NONE,
+ new int[0], new String[0], GLMediaPlayer.STREAM_ID_NONE,
+ new int[0], new String[0], GLMediaPlayer.STREAM_ID_NONE,
+ TestTexture.singleton.getWidth(), TestTexture.singleton.getHeight(), 0, 0, 0, _fps, _totalFrames, 0, _duration,
+ "png-static", null, null);
}
protected abstract TextureFrame createTexImage(GL gl, int texName);
@@ -1704,7 +1710,9 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
int vid, final int[] a_streams, final String[] a_langs,
int aid, final int[] s_streams, final String[] s_langs,
int sid, final int width, final int height,
- final int bps_stream, final int bps_video, final int bps_audio, final float fps, final int videoFrames, final int audioFrames, final int duration, final String vcodec, final String acodec) {
+ final int bps_stream, final int bps_video, final int bps_audio,
+ final float fps, final int videoFrames, final int audioFrames, final int duration,
+ final String vcodec, final String acodec, final String scodec) {
final GLMediaPlayer.EventMask eventMask = new GLMediaPlayer.EventMask();
final boolean wasUninitialized = state == State.Uninitialized;
@@ -1803,6 +1811,10 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
eventMask.setBit(GLMediaPlayer.EventMask.Bit.Codec);
this.vcodec = vcodec;
}
+ if( (null!=scodec && scodec.length()>0 && !this.scodec.equals(scodec)) ) {
+ eventMask.setBit(GLMediaPlayer.EventMask.Bit.Codec);
+ this.scodec = scodec;
+ }
if( eventMask.isZero() ) {
return;
}
@@ -1992,7 +2004,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
", Texture[count "+textureCount+", free "+freeVideoFrames+", dec "+decVideoFrames+", tagt "+toHexString(textureTarget)+", ifmt "+toHexString(textureInternalFormat)+", fmt "+toHexString(textureFormat)+", type "+toHexString(textureType)+"], "+
"Video[id "+vid+"/"+Arrays.toString(v_streams)+"/"+Arrays.toString(v_langs)+", <"+vcodec+">, "+width+"x"+height+", glOrient "+isInGLOrientation+", "+fps+" fps, "+frame_duration+" fdur, "+bps_video+" bps], "+
"Audio[id "+aid+"/"+Arrays.toString(a_streams)+"/"+Arrays.toString(a_langs)+", <"+acodec+">, "+bps_audio+" bps, "+audioFrames+" frames], "+
- "Subs[id "+sid+"/"+Arrays.toString(s_streams)+"/"+Arrays.toString(s_langs)+"], uri "+loc+camPath+"]";
+ "Subs[id "+sid+"/"+Arrays.toString(s_streams)+"/"+Arrays.toString(s_langs)+", <"+scodec+">], uri "+loc+camPath+"]";
}
@Override
@@ -2066,10 +2078,16 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer {
return eventListeners.toArray(new GLMediaEventListener[eventListeners.size()]);
}
}
-
private final Object eventListenersLock = new Object();
@Override
+ public final void setASSEventListener(final ASSEventListener l) {
+ this.assEventListener = l;
+ }
+ @Override
+ public final ASSEventListener getASSEventListener() { return assEventListener; }
+
+ @Override
public final Object getAttachedObject(final String name) {
return attachedObjects.get(name);
}
diff --git a/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java
index f0f06bf2a..44031372f 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/NullGLMediaPlayer.java
@@ -149,10 +149,12 @@ public class NullGLMediaPlayer extends GLMediaPlayerImpl {
final float _fps = 24f;
final int _duration = 10*60*1000; // msec
final int _totalFrames = (int) ( (_duration/1000)*_fps );
- updateAttributes("null", new int[] { 0 }, new String[] { "und" },
- 0 /* fake */, new int[0], new String[0],
- GLMediaPlayer.STREAM_ID_NONE, new int[0], new String[0],
- GLMediaPlayer.STREAM_ID_NONE, texData.getWidth(), texData.getHeight(), 0, 0, 0, _fps, _totalFrames, 0, _duration, "png-static", null);
+ updateAttributes("null",
+ new int[] { 0 }, new String[] { "und" }, 0 /* fake */,
+ new int[0], new String[0], GLMediaPlayer.STREAM_ID_NONE,
+ new int[0], new String[0], GLMediaPlayer.STREAM_ID_NONE,
+ texData.getWidth(), texData.getHeight(), 0, 0, 0, _fps, _totalFrames, 0, _duration,
+ "png-static", null, null);
}
@Override
protected final void initGLImpl(final GL gl) throws IOException, GLException {
diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java
index 05a0ddb64..cfe0f72af 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java
@@ -52,7 +52,7 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo {
private static final List<String> glueLibNames = new ArrayList<String>(); // none
- private static final int symbolCount = 61;
+ private static final int symbolCount = 63;
private static final String[] symbolNames = {
"avutil_version",
"avformat_version",
@@ -80,7 +80,9 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo {
"av_packet_unref",
"avcodec_send_packet", // 57
"avcodec_receive_frame", // 57
- /* +18 = 23 */
+ "avcodec_decode_subtitle2", // 52.23.0
+ "avsubtitle_free", // 52.82.0
+ /* +20 = 25 */
// libavutil
"av_pix_fmt_desc_get", // >= lavu 51.45
@@ -100,7 +102,7 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo {
"av_channel_layout_uninit", // >= 59 (opt)
"av_channel_layout_describe", // >= 59 (opt)
"av_opt_set_chlayout", // >= 59
- /* +16 = 40 */
+ /* +17 = 42 */
// libavformat
"avformat_alloc_context",
@@ -117,11 +119,11 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo {
"avformat_network_init", // 53.13.0 (opt)
"avformat_network_deinit", // 53.13.0 (opt)
"avformat_find_stream_info", // 53.3.0 (opt)
- /* +14 = 54 */
+ /* +14 = 56 */
// libavdevice
"avdevice_register_all", // supported in all versions (opt)
- /* +1 = 55 */
+ /* +1 = 57 */
// libswresample
"av_opt_set_sample_fmt", // actually lavu .. but exist only w/ swresample!
@@ -130,7 +132,7 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo {
"swr_free",
"swr_convert",
"swr_get_out_samples",
- /* +6 = 61 */
+ /* +6 = 63 */
};
// optional symbol names
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 464b8c29d..0cc36cc91 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java
@@ -40,11 +40,13 @@ import com.jogamp.common.av.AudioFormat;
import com.jogamp.common.av.AudioSink;
import com.jogamp.common.av.AudioSinkFactory;
import com.jogamp.common.av.TimeFrameI;
+import com.jogamp.common.os.Clock;
import com.jogamp.common.util.IOUtil;
import com.jogamp.common.util.PropertyAccess;
import com.jogamp.common.util.SecurityUtil;
import com.jogamp.gluegen.runtime.ProcAddressTable;
import com.jogamp.opengl.util.GLPixelStorageModes;
+import com.jogamp.opengl.util.av.ASSEventLine;
import com.jogamp.opengl.util.av.GLMediaPlayer;
import com.jogamp.opengl.util.texture.Texture;
@@ -999,5 +1001,17 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl {
audioSink.enqueueData( audio_pts, sampleData, data_size);
}
}
+ final void pushSubtitleText(final String text, final int pts, final int start_display_pts, final int end_display_pts) {
+ if( null != assEventListener ) {
+ if( start_display_pts > getPTS().get(Clock.currentMillis()) ) {
+ assEventListener.run( new ASSEventLine(ASSEventLine.Format.TEXT, text, start_display_pts, end_display_pts) );
+ }
+ }
+ }
+ final void pushSubtitleASS(final String ass, final int pts, final int start_display_pts, final int end_display_pts) {
+ if( null != assEventListener ) {
+ assEventListener.run( new ASSEventLine(ASSEventLine.Format.FFMPEG, ass, start_display_pts, end_display_pts) );
+ }
+ }
}
diff --git a/src/jogl/native/libav/ffmpeg_impl_template.c b/src/jogl/native/libav/ffmpeg_impl_template.c
index 1d938102f..e3a1590f4 100644
--- a/src/jogl/native/libav/ffmpeg_impl_template.c
+++ b/src/jogl/native/libav/ffmpeg_impl_template.c
@@ -69,6 +69,8 @@ typedef int (APIENTRYP AV_NEW_PACKET)(AVPacket *pkt, int size);
typedef void (APIENTRYP AV_PACKET_UNREF)(AVPacket *pkt);
typedef int (APIENTRYP AVCODEC_SEND_PACKET)(AVCodecContext *avctx, AVPacket *avpkt); // 57
typedef int (APIENTRYP AVCODEC_RECEIVE_FRAME)(AVCodecContext *avctx, AVFrame *picture); // 57
+typedef int (APIENTRYP AVCODEC_DECODE_SUBTITLE2)(AVCodecContext *avctx, AVSubtitle *sub, int *got_sub_ptr, const AVPacket *avpkt); // 52.23
+typedef int (APIENTRYP AV_SUBTITLE_FREE)(AVSubtitle *sub); // 52.82
static AVCODEC_CLOSE sp_avcodec_close;
static AVCODEC_STRING sp_avcodec_string;
@@ -89,7 +91,9 @@ static AV_PACKET_UNREF sp_av_packet_unref;
static AVCODEC_SEND_PACKET sp_avcodec_send_packet; // 57
static AVCODEC_RECEIVE_FRAME sp_avcodec_receive_frame; // 57
-// count: +18 = 23
+static AVCODEC_DECODE_SUBTITLE2 sp_avcodec_decode_subtitle2; // 52.23
+static AV_SUBTITLE_FREE sp_avsubtitle_free; // 52.82
+// count: +20 = 25
// libavutil
typedef AVPixFmtDescriptor* (APIENTRYP AV_PIX_FMT_DESC_GET)(enum AVPixelFormat pix_fmt); // lavu >= 51.45; lavu 51: 'enum PixelFormat pix_fmt', lavu 53: 'enum AVPixelFormat pix_fmt'
@@ -127,7 +131,7 @@ static AV_CHANNEL_LAYOUT_DEFAULT sp_av_channel_layout_default; // >= 59
static AV_CHANNEL_LAYOUT_UNINIT sp_av_channel_layout_uninit; // >= 59
static AV_CHANNEL_LAYOUT_DESCRIBE sp_av_channel_layout_describe; // >= 59
static AV_OPT_SET_CHLAYOUT sp_av_opt_set_chlayout; // >= 59
-// count: +17 = 40
+// count: +17 = 42
// libavformat
typedef AVFormatContext *(APIENTRYP AVFORMAT_ALLOC_CONTEXT)(void);
@@ -159,12 +163,12 @@ static AV_READ_PAUSE sp_av_read_pause;
static AVFORMAT_NETWORK_INIT sp_avformat_network_init; // 53.13.0
static AVFORMAT_NETWORK_DEINIT sp_avformat_network_deinit; // 53.13.0
static AVFORMAT_FIND_STREAM_INFO sp_avformat_find_stream_info; // 53.3.0
-// count: +14 = 54
+// count: +14 = 56
// libavdevice [53.0.0]
typedef int (APIENTRYP AVDEVICE_REGISTER_ALL)(void);
static AVDEVICE_REGISTER_ALL sp_avdevice_register_all;
-// count: +1 = 55
+// count: +1 = 57
// libswresample [1...]
typedef int (APIENTRYP AV_OPT_SET_SAMPLE_FMT)(void *obj, const char *name, enum AVSampleFormat fmt, int search_flags); // actually lavu .. but exist only w/ swresample!
@@ -180,7 +184,7 @@ static SWR_INIT sp_swr_init;
static SWR_FREE sp_swr_free;
static SWR_CONVERT sp_swr_convert;
static SWR_GET_OUT_SAMPLES sp_swr_get_out_samples;
-// count: +6 = 61
+// count: +6 = 66
static const char * const ClazzNameString = "java/lang/String";
@@ -206,7 +210,7 @@ static const char * const ClazzNameString = "java/lang/String";
#define MY_MUTEX_UNLOCK(e,s)
#endif
-#define SYMBOL_COUNT 61
+#define SYMBOL_COUNT 63
JNIEXPORT jboolean JNICALL FF_FUNC(initSymbols0)
(JNIEnv *env, jobject instance, jobject jmutex_avcodec_openclose, jobject jSymbols, jint count)
@@ -251,6 +255,8 @@ JNIEXPORT jboolean JNICALL FF_FUNC(initSymbols0)
sp_av_packet_unref = (AV_PACKET_UNREF) (intptr_t) symbols[i++];
sp_avcodec_send_packet = (AVCODEC_SEND_PACKET) (intptr_t) symbols[i++];
sp_avcodec_receive_frame = (AVCODEC_RECEIVE_FRAME) (intptr_t) symbols[i++];
+ sp_avcodec_decode_subtitle2 = (AVCODEC_DECODE_SUBTITLE2) (intptr_t) symbols[i++];
+ sp_avsubtitle_free = (AV_SUBTITLE_FREE) (intptr_t) symbols[i++];
sp_av_pix_fmt_desc_get = (AV_PIX_FMT_DESC_GET) (intptr_t) symbols[i++];
sp_av_frame_unref = (AV_FRAME_UNREF) (intptr_t) symbols[i++];
@@ -458,7 +464,8 @@ static void _updateJavaAttributes(JNIEnv *env, FFMPEGToolBasicAV_t* pAV) {
pAV->bps_stream, pAV->bps_video, pAV->bps_audio,
pAV->fps, pAV->frames_video, pAV->frames_audio, pAV->duration,
(*env)->NewStringUTF(env, pAV->vcodec),
- (*env)->NewStringUTF(env, pAV->acodec) );
+ (*env)->NewStringUTF(env, pAV->acodec),
+ (*env)->NewStringUTF(env, pAV->scodec));
JoglCommon_ExceptionCheck1_throwNewRuntimeException(env, "FFmpeg: Exception occured at updateAttributes(..)");
}
}
@@ -491,6 +498,14 @@ static void freeInstance(JNIEnv *env, FFMPEGToolBasicAV_t* pAV) {
}
pAV->pACodec=NULL;
+ // Close the S codec
+ if(NULL != pAV->pSCodecCtx) {
+ sp_avcodec_close(pAV->pSCodecCtx);
+ sp_avcodec_free_context(&pAV->pSCodecCtx);
+ pAV->pSCodecCtx = NULL;
+ }
+ pAV->pSCodec=NULL;
+
// Close the video file
if(NULL != pAV->pFormatCtx) {
sp_avformat_close_input(&pAV->pFormatCtx);
@@ -1018,6 +1033,7 @@ JNIEXPORT void JNICALL FF_FUNC(setStream0)
}
// Customize ..
+ pAV->pACodecCtx->pkt_timebase = pAV->pAStream->time_base;
// 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=0;
@@ -1028,7 +1044,6 @@ JNIEXPORT void JNICALL FF_FUNC(setStream0)
// Note: OpenAL well supports n-channel by now (SOFT),
// however - AFAIK AV_SAMPLE_FMT_S16 would allow no conversion!
pAV->pACodecCtx->request_sample_fmt=AV_SAMPLE_FMT_S16;
- pAV->pACodecCtx->skip_frame=AVDISCARD_DEFAULT;
sp_avcodec_string(pAV->acodec, sizeof(pAV->acodec), pAV->pACodecCtx, 0);
@@ -1209,6 +1224,7 @@ JNIEXPORT void JNICALL FF_FUNC(setStream0)
return;
}
// Customize ..
+ pAV->pVCodecCtx->pkt_timebase = pAV->pVStream->time_base;
// 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=0;
@@ -1314,8 +1330,56 @@ JNIEXPORT void JNICALL FF_FUNC(setStream0)
}
sp_av_frame_unref(pAV->pVFrame);
}
+
+ if(0<=pAV->sid) {
+ // Get a pointer to the codec context for the video stream
+ // FIXME: Libav Binary compatibility! JAU01
+ pAV->pSCodecPar = pAV->pSStream->codecpar;
+ #if 0
+ pAV->pSCodecCtx->get_format = my_get_format;
+ #endif
+
+ // Find the decoder for the video stream
+ pAV->pSCodec=sp_avcodec_find_decoder(pAV->pSCodecPar->codec_id);
+ if(pAV->pSCodec==NULL) {
+ JoglCommon_throwNewRuntimeException(env, "Couldn't find subtitle codec for codec_id %d", pAV->pSCodecPar->codec_id);
+ return;
+ }
+
+ // Allocate the decoder context for the video stream
+ pAV->pSCodecCtx = sp_avcodec_alloc_context3(pAV->pSCodec);
+ if(pAV->pSCodecCtx==NULL) {
+ JoglCommon_throwNewRuntimeException(env, "Couldn't allocate subtitle decoder context for codec_id %d", pAV->pSCodecPar->codec_id);
+ return;
+ }
+ res = sp_avcodec_parameters_to_context(pAV->pSCodecCtx, pAV->pSCodecPar);
+ if(res<0) {
+ JoglCommon_throwNewRuntimeException(env, "Couldn't copy video codec-par to context");
+ return;
+ }
+ // Customize ..
+ pAV->pSCodecCtx->pkt_timebase = pAV->pSStream->time_base;
+ pAV->pVCodecCtx->thread_count=0;
+ pAV->pVCodecCtx->thread_type=0;
+ pAV->pVCodecCtx->workaround_bugs=FF_BUG_AUTODETECT;
+ pAV->pVCodecCtx->skip_frame=AVDISCARD_DEFAULT;
+
+ sp_avcodec_string(pAV->scodec, sizeof(pAV->scodec), pAV->pSCodecCtx, 0);
+
+ // Open codec
+ MY_MUTEX_LOCK(env, mutex_avcodec_openclose);
+ {
+ res = sp_avcodec_open2(pAV->pSCodecCtx, pAV->pSCodec, NULL);
+ }
+ MY_MUTEX_UNLOCK(env, mutex_avcodec_openclose);
+ if(res<0) {
+ JoglCommon_throwNewRuntimeException(env, "Couldn't open subtitle codec %d, %s", pAV->pSCodecCtx->codec_id, pAV->scodec);
+ return;
+ }
+ }
pAV->vPTS=0;
pAV->aPTS=0;
+ pAV->sPTS=0;
initPTSStats(&pAV->vPTSStats);
initPTSStats(&pAV->aPTSStats);
pAV->ready = 1;
@@ -1711,6 +1775,76 @@ JNIEXPORT jint JNICALL FF_FUNC(readNextPacket0)
sp_av_frame_unref(pAV->pVFrame);
} // draining frames loop
+ } else if(stream_id == pAV->sid) {
+ // Decode Subtitle package
+ int res = 0;
+ int got_sub = 0, got_sub2 = 0;
+ AVSubtitle sub;
+
+ res = sp_avcodec_decode_subtitle2(pAV->pSCodecCtx, &sub, &got_sub, pAV->packet);
+ if (0 > res) {
+ res = 0;
+ if( pAV->verbose ) {
+ fprintf(stderr, "S-P: EOF.0\n");
+ }
+ } else {
+ // OK
+ if( !got_sub ) {
+ if( pAV->packet->data ) {
+ // EAGAIN
+ } else {
+ // EOF
+ if( pAV->verbose ) {
+ fprintf(stderr, "S-P: EOF.1\n");
+ }
+ }
+ } else {
+ if (!pAV->packet->data) {
+ // .. pending ..
+ if( pAV->verbose ) {
+ fprintf(stderr, "S-P: Pending\n");
+ }
+ } else {
+ got_sub2 = 1;
+ }
+ }
+ }
+ if( got_sub2 ) {
+ int32_t sPTS, sStart, sEnd;
+ if( AV_NOPTS_VALUE == sub.pts ) {
+ sPTS = -1;
+ sStart = -1;
+ sEnd = -1;
+ } else {
+ sPTS = my_av_q2i32( sub.pts * 1000, AV_TIME_BASE_Q);
+ sStart = my_av_q2i32( ( sub.pts + sub.start_display_time ) * 1000, AV_TIME_BASE_Q);
+ sEnd = my_av_q2i32( ( sub.pts + sub.end_display_time ) * 1000, AV_TIME_BASE_Q);
+ }
+ for(unsigned int i=0; i<sub.num_rects; ++i) {
+ AVSubtitleRect* r = sub.rects[i];
+ if( SUBTITLE_TEXT == r->type && NULL != r->text ) {
+ if( pAV->verbose ) {
+ fprintf(stderr, "S[f %d, i %d, pts %d[%d..%d]]: %s\n", (int)r->type, i, r->text, sPTS, sStart, sEnd);
+ }
+ (*env)->CallVoidMethod(env, pAV->ffmpegMediaPlayer, ffmpeg_jni_mid_pushSubtitleText, (*env)->NewStringUTF(env, r->text), sPTS, sStart, sEnd);
+ JoglCommon_ExceptionCheck1_throwNewRuntimeException(env, "FFmpeg: Exception occured at pushSubtitleText(..)");
+ } else if( SUBTITLE_ASS == r->type && NULL != r->ass ) {
+ if( pAV->verbose ) {
+ fprintf(stderr, "S[f %d, i %d, pts %d[%d..%d]]: %s\n", (int)r->type, i, r->ass, sPTS, sStart, sEnd);
+ }
+ (*env)->CallVoidMethod(env, pAV->ffmpegMediaPlayer, ffmpeg_jni_mid_pushSubtitleASS, (*env)->NewStringUTF(env, r->ass), sPTS, sStart, sEnd);
+ JoglCommon_ExceptionCheck1_throwNewRuntimeException(env, "FFmpeg: Exception occured at pushSubtitleASS(..)");
+ } else {
+ if( pAV->verbose ) {
+ fprintf(stderr, "S[f %d, i %d]: null\n", (int)r->type, i);
+ }
+ }
+ }
+ pAV->sPTS = sPTS;
+ }
+ if( got_sub ) {
+ sp_avsubtitle_free(&sub);
+ }
} // stream_id selection
sp_av_packet_unref(pAV->packet);
}
@@ -1817,6 +1951,9 @@ JNIEXPORT jint JNICALL FF_FUNC(seek0)
if(NULL != pAV->pACodecCtx) {
sp_avcodec_flush_buffers( pAV->pACodecCtx );
}
+ if(NULL != pAV->pSCodecCtx) {
+ sp_avcodec_flush_buffers( pAV->pSCodecCtx );
+ }
const jint rPTS = my_av_q2i32( ( pAV->vid >= 0 ? pAV->pVFrame->pts : pAV->pAFrames[pAV->aFrameCurrent]->pts ) * 1000, time_base);
if(pAV->verbose) {
fprintf(stderr, "SEEK: post : res %d, u %d\n", res, rPTS);
diff --git a/src/jogl/native/libav/ffmpeg_static.c b/src/jogl/native/libav/ffmpeg_static.c
index 10b388e75..184972306 100644
--- a/src/jogl/native/libav/ffmpeg_static.c
+++ b/src/jogl/native/libav/ffmpeg_static.c
@@ -36,6 +36,8 @@ static const char * const ClazzNameFFMPEGMediaPlayer = "jogamp/opengl/util/av/im
static jclass ffmpegMediaPlayerClazz = NULL;
jmethodID ffmpeg_jni_mid_pushSound = NULL;
+jmethodID ffmpeg_jni_mid_pushSubtitleText = NULL;
+jmethodID ffmpeg_jni_mid_pushSubtitleASS = NULL;
jmethodID ffmpeg_jni_mid_updateAttributes = NULL;
jmethodID ffmpeg_jni_mid_setIsGLOriented = NULL;
jmethodID ffmpeg_jni_mid_setupFFAttributes = NULL;
@@ -65,13 +67,17 @@ JNIEXPORT jboolean JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGStaticNatives_i
}
ffmpeg_jni_mid_pushSound = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "pushSound", "(Ljava/nio/ByteBuffer;II)V");
+ ffmpeg_jni_mid_pushSubtitleText = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "pushSubtitleText", "(Ljava/lang/String;III)V");
+ ffmpeg_jni_mid_pushSubtitleASS = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "pushSubtitleASS", "(Ljava/lang/String;III)V");
ffmpeg_jni_mid_updateAttributes = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "updateAttributes",
- "(Ljava/lang/String;[I[Ljava/lang/String;I[I[Ljava/lang/String;I[I[Ljava/lang/String;IIIIIIFIIILjava/lang/String;Ljava/lang/String;)V");
+ "(Ljava/lang/String;[I[Ljava/lang/String;I[I[Ljava/lang/String;I[I[Ljava/lang/String;IIIIIIFIIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
ffmpeg_jni_mid_setIsGLOriented = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "setIsGLOriented", "(Z)V");
ffmpeg_jni_mid_setupFFAttributes = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "setupFFAttributes", "(IIIIIIIIIIIIIII)V");
ffmpeg_jni_mid_isAudioFormatSupported = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "isAudioFormatSupported", "(III)Z");
if(ffmpeg_jni_mid_pushSound == NULL ||
+ ffmpeg_jni_mid_pushSubtitleText == NULL ||
+ ffmpeg_jni_mid_pushSubtitleASS == NULL ||
ffmpeg_jni_mid_updateAttributes == NULL ||
ffmpeg_jni_mid_setIsGLOriented == NULL ||
ffmpeg_jni_mid_setupFFAttributes == NULL ||
diff --git a/src/jogl/native/libav/ffmpeg_static.h b/src/jogl/native/libav/ffmpeg_static.h
index ab4db2506..c77783424 100644
--- a/src/jogl/native/libav/ffmpeg_static.h
+++ b/src/jogl/native/libav/ffmpeg_static.h
@@ -41,6 +41,8 @@
#include "jogamp_opengl_util_av_impl_FFMPEGStaticNatives.h"
extern jmethodID ffmpeg_jni_mid_pushSound;
+extern jmethodID ffmpeg_jni_mid_pushSubtitleText;
+extern jmethodID ffmpeg_jni_mid_pushSubtitleASS;
extern jmethodID ffmpeg_jni_mid_updateAttributes;
extern jmethodID ffmpeg_jni_mid_setIsGLOriented;
extern jmethodID ffmpeg_jni_mid_setupFFAttributes;
diff --git a/src/jogl/native/libav/ffmpeg_tool.h b/src/jogl/native/libav/ffmpeg_tool.h
index 5e23ca882..852ff8d1e 100644
--- a/src/jogl/native/libav/ffmpeg_tool.h
+++ b/src/jogl/native/libav/ffmpeg_tool.h
@@ -200,6 +200,10 @@ typedef struct {
int32_t s_streams[MAX_STREAM_COUNT];
int32_t sid;
AVStream* pSStream;
+ AVCodecParameters* pSCodecPar;
+ AVCodecContext* pSCodecCtx;
+ AVCodec* pSCodec;
+ int32_t sPTS; // msec - overall last subtitle PTS
float fps; // frames per seconds
int32_t bps_stream; // bits per seconds
@@ -212,6 +216,7 @@ typedef struct {
char acodec[64];
char vcodec[64];
+ char scodec[64];
int32_t ready;