aboutsummaryrefslogtreecommitdiffstats
path: root/src/jogl/classes/com/jogamp/opengl/util/av/SubTextEvent.java
diff options
context:
space:
mode:
authorSven Göthel <[email protected]>2024-02-02 08:27:49 +0100
committerSven Göthel <[email protected]>2024-02-02 08:27:49 +0100
commit23cf5279472d3ae1b2d8d1904e6b1f1e7fd8f012 (patch)
tree7f943da4a1a6c8fd917eff160b45b700a125f258 /src/jogl/classes/com/jogamp/opengl/util/av/SubTextEvent.java
parent9ff736464e0d2762c424bab66bc6d03ccc6e6d11 (diff)
Bug 1494 - GLMediaPlayer/GraphUI: Support Displaying Bitmap'ed Subtitles (PGS ..) via FFMPEGFMediaPlayer/FFmpeg
FFMPEGFMediaPlayer related changes: - Add libswscale (6th FFmpeg lib used) for sws_getCachedContext(), sws_scale() and sws_freeContext(), used natively to convert the palette'ed bitmap into RGBA colorspace -> GL texture - Handling AVSubtitleRect.type SUBTITLE_BITMAP -- only handled if libswscale is available -- config/adjust texture object -- sws_scale palette'ed bitmap to texture -- intermediate memory is cached, may be resized and free'ed at destroy -- texture objects are managed and passed from GLMediaPlayerImpl, as they are also forwarded to player client via SubBitmapEvent - Passing the AVCodecID to GLMediaPlayerImpl, converted to our CodecID enum. - Unifying creation and opening of AVCodecContext with 'createOpenedAVCodecContext(..)' +++ SubtitleEvent* - SubTextEvent now also handles ASS.Dialogue (FFmpeg 4) besides ASS.Event (FFmpeg 5, 6, ..). +++ GLMediaPlayerImpl - Added ringbuffer subTexFree, managing Texture for bitmap'ed subtitles -- Uses 1 bitmap-subtitle Texture per used textureCount in cache, as one bitmap-subtile can be displayed per frame. Could be potentially reduced to just 2 .. but resources used are relatively low here. - Validating subTexFree + videoFramesFree usage, use blocking get/put ringbuffer due to utilization from different threads. - Receives subtitle content from native getNextPacket0() via callback, creates SubtitleEvent instance and passes it to a SubtitleEventListener - if exists. (See MediaButton example) -- SubBitmapEvent also gets its special SubBitmapEvent.TextureOwner to handle client releasing the event and allowing us to put back the Texture resource to 'subTexFree'. This passing through of the Texture object is probably a weakness of this lifecycle and requires the client to ensure SubtitleEvent.release() gets called. See MediaButton example! - Exposing CodecID, allowing clients like MediaButton to handle SubtitleEvent content according to codec
Diffstat (limited to 'src/jogl/classes/com/jogamp/opengl/util/av/SubTextEvent.java')
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/av/SubTextEvent.java243
1 files changed, 243 insertions, 0 deletions
diff --git a/src/jogl/classes/com/jogamp/opengl/util/av/SubTextEvent.java b/src/jogl/classes/com/jogamp/opengl/util/av/SubTextEvent.java
new file mode 100644
index 000000000..d699d9300
--- /dev/null
+++ b/src/jogl/classes/com/jogamp/opengl/util/av/SubTextEvent.java
@@ -0,0 +1,243 @@
+/**
+ * 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;
+
+import java.time.format.DateTimeParseException;
+
+import com.jogamp.common.av.PTS;
+
+/**
+ * Text Event Line including ASS/SAA of {@link SubtitleEvent}
+ * <p>
+ * See http://www.tcax.org/docs/ass-specs.htm
+ * </p>
+ */
+public class SubTextEvent extends SubtitleEvent {
+ /** Text formatting */
+ public enum TextFormat {
+ /** Multiple ASS formats may be passed, see {@link ASSType}. */
+ ASS,
+ /** Just plain text */
+ TEXT,
+ };
+ /** ASS Formatting Type */
+ public enum ASSType {
+ /**
+ * ASS dialogue-line output w/ start and end (Given by FFmpeg 4.*)
+ * <pre>
+ 0 1 2 3 4 5 6 7 8 9
+ Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
+
+ 'Dialogue: 0,0:02:02.15,0:02:02.16,Default,,0,0,0,,trying to force him to travel to that'
+ * </pre>
+ */
+ DIALOGUE,
+ /**
+ * FFMpeg ASS event-line output w/o start, end (Given by FFmpeg 5.*, 6.*, ..)
+ * <pre>
+ 0 1 2 3 4 5 6 7 8
+ Seq, Layer, Style, Name, MarginL, MarginR, MarginV, Effect, TEXT
+ * </pre>
+ */
+ EVENT,
+ /** Just plain text */
+ TEXT
+ }
+ /** {@link TextFormat} of this text subtitle event. */
+ public final TextFormat textFormat;
+ /** {@link ASSType} sub-type */
+ public final ASSType assType;
+ /** Start time in milliseconds, or -1. */
+ public final int start;
+ /** End time in milliseconds, or -1. */
+ public final int end;
+ public final String style;
+
+ public final int seqnr;
+ public final int layer;
+
+ public final String name;
+ public final String effect;
+ /** Actual subtitle text */
+ public final String text;
+ /** Number of lines of {@link #text}, i.e. occurrence of {@code \n} + 1. */
+ public final int lines;
+
+ private static boolean DEBUG = false;
+
+ /**
+ * ASS/SAA Event Line ctor
+ * @param codec the {@link CodecID}
+ * @param fmt input format of {@code ass}, currently only {@link SubTextEvent.TextFormat#ASS} and {@link SubTextEvent.TextFormat#TEXT} is supported
+ * @param ass ASS/SAA compatible event line according to {@link ASSType}
+ * @param pts_start pts start in ms, provided for {@link SubTextEvent.TextFormat#ASS} and {@link SubTextEvent.TextFormat#TEXT}
+ * @param pts_end pts end in ms, provided for {@link SubTextEvent.TextFormat#ASS} and {@link SubTextEvent.TextFormat#TEXT}
+ */
+ public SubTextEvent(final CodecID codec, final TextFormat fmt, final String ass, final int pts_start, final int pts_end) {
+ super(SubtitleEvent.Type.Text, codec, pts_start, pts_end);
+ this.textFormat = fmt;
+ ASSType assType = ASSType.TEXT;
+ int start = -1;
+ int end = -1;
+ int seqnr = 0;
+ int layer = 0;
+ String style = "Default";
+ String name = "";
+ String effect = "";
+ String text = "";
+ boolean done = false;
+ if( TextFormat.ASS == fmt ) {
+ final int len = null != ass ? ass.length() : 0;
+ {
+ // ASSType.DIALOGUE
+ int part = 0;
+ for(int i=0; 10 > part && len > i; ) {
+ if( 9 == part ) {
+ text = ass.substring(i);
+ done = true;
+ assType = ASSType.DIALOGUE;
+ } else {
+ final int j = ass.indexOf(',', i);
+ if( 0 > j ) {
+ break;
+ }
+ final String v = ass.substring(i, j);
+ try {
+ switch(part) {
+ case 1:
+ start = PTS.toMillis(v, true);
+ break;
+ case 2:
+ end = PTS.toMillis(v, true);
+ break;
+ case 3:
+ style = v;
+ break;
+ case 4:
+ name = v;
+ break;
+ case 8:
+ effect = v;
+ break;
+ }
+ } catch(final DateTimeParseException pe) {
+ if( DEBUG ) {
+ System.err.println("ASS.DIALG parsing error of part "+part+" '"+v+"' of '"+ass+"'");
+ }
+ break;
+ }
+ i = j + 1;
+ }
+ ++part;
+ }
+ }
+ if( !done ) {
+ // ASSType.EVENT
+ int part = 0;
+ for(int i=0; 9 > part && len > i; ) {
+ if( 8 == part ) {
+ text = ass.substring(i);
+ done = true;
+ assType = ASSType.EVENT;
+ } else {
+ final int j = ass.indexOf(',', i);
+ if( 0 > j ) {
+ break;
+ }
+ final String v = ass.substring(i, j);
+ try {
+ 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;
+ case 7:
+ effect = v;
+ break;
+ }
+ } catch(final NumberFormatException nfe) {
+ if( DEBUG ) {
+ System.err.println("ASS.EVENT parsing error of part "+part+" '"+v+"' of '"+ass+"'");
+ }
+ break;
+ }
+ i = j + 1;
+ }
+ ++part;
+ }
+ }
+ }
+ if( !done && TextFormat.TEXT == fmt ) {
+ text = ass;
+ done = true;
+ assType = ASSType.TEXT;
+ }
+ this.assType = assType;
+ this.start = start;
+ this.end = end;
+ this.seqnr = seqnr;
+ this.layer = layer;
+ this.style = style;
+ this.name = name;
+ this.effect = effect;
+ 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;
+ }
+ }
+
+ @Override
+ public void release() {} // nothing to be released back to the owner
+
+ @Override
+ public String toString() {
+ final String start_s = 0 <= start ? PTS.toTimeStr(start, true) : "undef";
+ final String end_s = 0 <= end ? PTS.toTimeStr(end, true) : "undef";
+ final String fms_s = TextFormat.ASS == textFormat ? "ASS("+assType+")" : textFormat.toString();
+ return getStartString()+", "+fms_s+", #"+seqnr+", l_"+layer+
+ ", ["+start_s+".."+end_s+"], style "+style+", name '"+name+"', effect '"+effect+"': '"+text+"' ("+lines+")]";
+ }
+}