diff --git a/library/src/main/java/com/google/android/exoplayer/text/SimpleSubtitleParser.java b/library/src/main/java/com/google/android/exoplayer/text/SimpleSubtitleParser.java index b36a05c54a..ba6bc75623 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/SimpleSubtitleParser.java +++ b/library/src/main/java/com/google/android/exoplayer/text/SimpleSubtitleParser.java @@ -22,13 +22,19 @@ import com.google.android.exoplayer.util.extensions.SimpleDecoder; * Base class for subtitle parsers that use their own decode thread. */ public abstract class SimpleSubtitleParser extends - SimpleDecoder { + SimpleDecoder implements + SubtitleParser { protected SimpleSubtitleParser() { super(new SubtitleInputBuffer[2], new SubtitleOutputBuffer[2]); setInitialInputBufferSize(1024); } + @Override + public void setPositionUs(long timeUs) { + // Do nothing + } + @Override protected final SubtitleInputBuffer createInputBuffer() { return new SubtitleInputBuffer(); diff --git a/library/src/main/java/com/google/android/exoplayer/text/SubtitleParser.java b/library/src/main/java/com/google/android/exoplayer/text/SubtitleParser.java new file mode 100644 index 0000000000..1928471d33 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/text/SubtitleParser.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer.text; + +import com.google.android.exoplayer.ParserException; +import com.google.android.exoplayer.util.extensions.Decoder; + +/** + * Parses {@link Subtitle}s from {@link SubtitleInputBuffer}s. + */ +public interface SubtitleParser extends + Decoder { + + /** + * Informs the parser of the current playback position. + *

+ * Must be called prior to each attempt to dequeue output buffers from the parser. + * + * @param positionUs The current playback position in microseconds. + */ + public void setPositionUs(long positionUs); + +} diff --git a/library/src/main/java/com/google/android/exoplayer/text/SubtitleParserFactory.java b/library/src/main/java/com/google/android/exoplayer/text/SubtitleParserFactory.java index e61e2d8849..0d00578489 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/SubtitleParserFactory.java +++ b/library/src/main/java/com/google/android/exoplayer/text/SubtitleParserFactory.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer.text; import com.google.android.exoplayer.Format; -import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.extensions.Decoder; @@ -41,7 +40,7 @@ public interface SubtitleParserFactory { * @return A new {@link Decoder}. * @throws IllegalArgumentException If the {@link Format} is not supported. */ - Decoder createParser(Format format); + SubtitleParser createParser(Format format); /** * Default {@link SubtitleParserFactory} implementation. @@ -63,16 +62,14 @@ public interface SubtitleParserFactory { return getParserClass(format.sampleMimeType) != null; } - @SuppressWarnings("unchecked") @Override - public Decoder createParser( - Format format) { + public SubtitleParser createParser(Format format) { try { Class clazz = getParserClass(format.sampleMimeType); if (clazz == null) { throw new IllegalArgumentException("Attempted to create parser for unsupported format"); } - return clazz.asSubclass(Decoder.class).newInstance(); + return clazz.asSubclass(SubtitleParser.class).newInstance(); } catch (Exception e) { throw new IllegalStateException("Unexpected error instantiating parser", e); } diff --git a/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java index 4c3fcac0d9..e9b6e03f06 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java @@ -55,7 +55,7 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback { private boolean inputStreamEnded; private boolean outputStreamEnded; - private Decoder parser; + private SubtitleParser parser; private SubtitleInputBuffer nextInputBuffer; private SubtitleOutputBuffer subtitle; private SubtitleOutputBuffer nextSubtitle; @@ -133,6 +133,7 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback { } if (nextSubtitle == null) { + parser.setPositionUs(positionUs); try { nextSubtitle = parser.dequeueOutputBuffer(); } catch (IOException e) { diff --git a/library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608Parser.java b/library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608Parser.java index fd5aab0ec7..43b0780dd5 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608Parser.java +++ b/library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608Parser.java @@ -19,10 +19,12 @@ import com.google.android.exoplayer.C; import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.text.SubtitleInputBuffer; import com.google.android.exoplayer.text.SubtitleOutputBuffer; +import com.google.android.exoplayer.text.SubtitleParser; import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.ParsableBitArray; import com.google.android.exoplayer.util.ParsableByteArray; -import com.google.android.exoplayer.util.extensions.Decoder; + +import android.text.TextUtils; import java.util.LinkedList; import java.util.TreeSet; @@ -31,8 +33,7 @@ import java.util.TreeSet; * Facilitates the extraction and parsing of EIA-608 (a.k.a. "line 21 captions" and "CEA-608") * Closed Captions from the SEI data block from H.264. */ -public final class Eia608Parser implements - Decoder { +public final class Eia608Parser implements SubtitleParser { private static final int NUM_INPUT_BUFFERS = 10; private static final int NUM_OUTPUT_BUFFERS = 2; @@ -170,6 +171,8 @@ public final class Eia608Parser implements private final StringBuilder captionStringBuilder; + private long playbackPositionUs; + private SubtitleInputBuffer dequeuedInputBuffer; private int captionMode; @@ -201,6 +204,11 @@ public final class Eia608Parser implements captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT; } + @Override + public void setPositionUs(long positionUs) { + playbackPositionUs = positionUs; + } + @Override public SubtitleInputBuffer dequeueInputBuffer() throws ParserException { Assertions.checkState(dequeuedInputBuffer == null); @@ -221,34 +229,43 @@ public final class Eia608Parser implements @Override public SubtitleOutputBuffer dequeueOutputBuffer() throws ParserException { - if (queuedInputBuffers.isEmpty() || availableOutputBuffers.isEmpty()) { + if (availableOutputBuffers.isEmpty()) { return null; } - SubtitleOutputBuffer outputBuffer = null; - SubtitleInputBuffer inputBuffer = queuedInputBuffers.pollFirst(); + // iterate through all available input buffers whose timestamps are less than or equal + // to the current playback position; processing input buffers for future content should + // be deferred until they would be applicable + while (!queuedInputBuffers.isEmpty() + && queuedInputBuffers.first().timeUs <= playbackPositionUs) { + SubtitleInputBuffer inputBuffer = queuedInputBuffers.pollFirst(); - // TODO: investigate ways of batching multiple SubtitleInputBuffers into a single - // SubtitleOutputBuffer; it isn't as simple as just iterating through all of the queued input - // buffers until there is a change because for pop-on captions this will result in the input - // buffers being processed almost sequentially as they are queued, eliminating the re-ordering, - // resulting in the content having characters out of order - if (inputBuffer.isEndOfStream()) { - outputBuffer = availableOutputBuffers.pollFirst(); - outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); - } else if (decode(inputBuffer) - && ((captionString == null && lastCaptionString != null) - || (captionString != null && !captionString.equals((lastCaptionString))))) { - lastCaptionString = captionString; - outputBuffer = availableOutputBuffers.pollFirst(); - outputBuffer.setOutput(inputBuffer.timeUs, new Eia608Subtitle(captionString), 0); - if (inputBuffer.isDecodeOnly()) { - outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); + // If the input buffer indicates we've reached the end of the stream, we can + // return immediately with an output buffer propagating that + if (inputBuffer.isEndOfStream()) { + SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst(); + outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); + releaseInputBuffer(inputBuffer); + return outputBuffer; } + + decode(inputBuffer); + + // check if we have any caption updates to report + if (!TextUtils.equals(captionString, lastCaptionString)) { + lastCaptionString = captionString; + if (!inputBuffer.isDecodeOnly()) { + SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst(); + outputBuffer.setOutput(inputBuffer.timeUs, new Eia608Subtitle(captionString), 0); + releaseInputBuffer(inputBuffer); + return outputBuffer; + } + } + + releaseInputBuffer(inputBuffer); } - releaseInputBuffer(inputBuffer); - return outputBuffer; + return null; } private void releaseInputBuffer(SubtitleInputBuffer inputBuffer) { @@ -265,6 +282,7 @@ public final class Eia608Parser implements public void flush() { setCaptionMode(CC_MODE_UNKNOWN); captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT; + playbackPositionUs = 0; captionStringBuilder.setLength(0); captionString = null; lastCaptionString = null; @@ -285,9 +303,9 @@ public final class Eia608Parser implements // Do nothing } - private boolean decode(SubtitleInputBuffer inputBuffer) { + private void decode(SubtitleInputBuffer inputBuffer) { if (inputBuffer.size < 10) { - return false; + return; } seiBuffer.reset(inputBuffer.data.array()); @@ -363,18 +381,14 @@ public final class Eia608Parser implements } } - if (!captionDataProcessed) { - return false; + if (captionDataProcessed) { + if (!isRepeatableControl) { + repeatableControlSet = false; + } + if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) { + captionString = getDisplayCaption(); + } } - - if (!isRepeatableControl) { - repeatableControlSet = false; - } - if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) { - captionString = getDisplayCaption(); - } - - return true; } private boolean handleCtrl(byte cc1, byte cc2) {