diff options
Diffstat (limited to 'src/jogl/classes')
8 files changed, 242 insertions, 27 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) ); + } + } } |