Commit 0bd26b74 by cdrolle Committed by Oliver Woodman

Remove Eia608TrackRenderer

Functionality is moved into Eia608Parser, so that Eia608Parser
can be used with TextTrackRenderer, similar to all the other
text/subtitle formats. Modified some of the other
text/subtitle-related classes to support the new behaviour.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=120755145
parent 6c452336
Showing with 499 additions and 432 deletions
...@@ -208,7 +208,6 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer { ...@@ -208,7 +208,6 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer {
long startElapsedRealtimeMs = SystemClock.elapsedRealtime(); long startElapsedRealtimeMs = SystemClock.elapsedRealtime();
decoder = new VpxDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE); decoder = new VpxDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE);
decoder.setOutputMode(outputMode); decoder.setOutputMode(outputMode);
decoder.start();
notifyDecoderInitialized(startElapsedRealtimeMs, SystemClock.elapsedRealtime()); notifyDecoderInitialized(startElapsedRealtimeMs, SystemClock.elapsedRealtime());
codecCounters.codecInitCount++; codecCounters.codecInitCount++;
} }
......
/*
* 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;
/**
* A {@link Subtitle} output from a subtitle parser that extends {@link SimpleSubtitleParser}.
*/
public final class SimpleSubtitleOutputBuffer extends SubtitleOutputBuffer {
private SimpleSubtitleParser owner;
public SimpleSubtitleOutputBuffer(SimpleSubtitleParser owner) {
super();
this.owner = owner;
}
@Override
public final void release() {
owner.releaseOutputBuffer(this);
}
}
...@@ -19,12 +19,12 @@ import com.google.android.exoplayer.ParserException; ...@@ -19,12 +19,12 @@ import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.util.extensions.SimpleDecoder; import com.google.android.exoplayer.util.extensions.SimpleDecoder;
/** /**
* Parses {@link Subtitle}s from {@link SubtitleInputBuffer}s. * Base class for subtitle parsers that use their own decode thread.
*/ */
public abstract class SubtitleParser extends public abstract class SimpleSubtitleParser extends
SimpleDecoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException> { SimpleDecoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException> {
protected SubtitleParser() { protected SimpleSubtitleParser() {
super(new SubtitleInputBuffer[2], new SubtitleOutputBuffer[2]); super(new SubtitleInputBuffer[2], new SubtitleOutputBuffer[2]);
setInitialInputBufferSize(1024); setInitialInputBufferSize(1024);
} }
...@@ -36,7 +36,7 @@ public abstract class SubtitleParser extends ...@@ -36,7 +36,7 @@ public abstract class SubtitleParser extends
@Override @Override
protected final SubtitleOutputBuffer createOutputBuffer() { protected final SubtitleOutputBuffer createOutputBuffer() {
return new SubtitleOutputBuffer(this); return new SimpleSubtitleOutputBuffer(this);
} }
@Override @Override
...@@ -56,6 +56,14 @@ public abstract class SubtitleParser extends ...@@ -56,6 +56,14 @@ public abstract class SubtitleParser extends
} }
} }
/**
* Decodes the data and converts it into a {@link Subtitle}.
*
* @param data The data to be decoded.
* @param size The size of the data.
* @return A {@link Subtitle} to rendered.
* @throws ParserException A parsing exception.
*/
protected abstract Subtitle decode(byte[] data, int size) throws ParserException; protected abstract Subtitle decode(byte[] data, int size) throws ParserException;
} }
...@@ -18,9 +18,10 @@ package com.google.android.exoplayer.text; ...@@ -18,9 +18,10 @@ package com.google.android.exoplayer.text;
import com.google.android.exoplayer.DecoderInputBuffer; import com.google.android.exoplayer.DecoderInputBuffer;
/** /**
* An input buffer for {@link SubtitleParser}. * An input buffer for a subtitle parser.
*/ */
/* package */ final class SubtitleInputBuffer extends DecoderInputBuffer { public final class SubtitleInputBuffer extends DecoderInputBuffer
implements Comparable<SubtitleInputBuffer> {
public long subsampleOffsetUs; public long subsampleOffsetUs;
...@@ -28,4 +29,13 @@ import com.google.android.exoplayer.DecoderInputBuffer; ...@@ -28,4 +29,13 @@ import com.google.android.exoplayer.DecoderInputBuffer;
super(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); super(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
} }
@Override
public int compareTo(SubtitleInputBuffer other) {
long delta = timeUs - other.timeUs;
if (delta == 0) {
return 0;
}
return delta > 0 ? 1 : -1;
}
} }
...@@ -21,17 +21,14 @@ import com.google.android.exoplayer.util.extensions.OutputBuffer; ...@@ -21,17 +21,14 @@ import com.google.android.exoplayer.util.extensions.OutputBuffer;
import java.util.List; import java.util.List;
/** /**
* A {@link Subtitle} output from a {@link SubtitleParser}. * Base class for a {@link Subtitle} output from a subtitle parser.
*/ */
/* package */ final class SubtitleOutputBuffer extends OutputBuffer implements Subtitle { public abstract class SubtitleOutputBuffer extends OutputBuffer implements Subtitle {
private final SubtitleParser owner;
private Subtitle subtitle; private Subtitle subtitle;
private long offsetUs; private long offsetUs;
public SubtitleOutputBuffer(SubtitleParser owner) { public SubtitleOutputBuffer() {
this.owner = owner;
} }
public void setOutput(long timestampUs, Subtitle subtitle, long subsampleOffsetUs) { public void setOutput(long timestampUs, Subtitle subtitle, long subsampleOffsetUs) {
...@@ -62,9 +59,7 @@ import java.util.List; ...@@ -62,9 +59,7 @@ import java.util.List;
} }
@Override @Override
public void release() { public abstract void release();
owner.releaseOutputBuffer(this);
}
@Override @Override
public void clear() { public void clear() {
......
...@@ -16,30 +16,37 @@ ...@@ -16,30 +16,37 @@
package com.google.android.exoplayer.text; package com.google.android.exoplayer.text;
import com.google.android.exoplayer.Format; 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.MimeTypes;
import com.google.android.exoplayer.util.extensions.Decoder;
/** /**
* A factory for {@link SubtitleParser} instances. * A factory for {@link Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException>}
* instances that will parse subtitles.
*/ */
public interface SubtitleParserFactory { public interface SubtitleParserFactory {
/** /**
* Returns whether the factory is able to instantiate a {@link SubtitleParser} for the given * Returns whether the factory is able to instantiate a
* {@link Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException>} for the given
* {@link Format}. * {@link Format}.
* *
* @param format The {@link Format}. * @param format The {@link Format}.
* @return True if the factory can instantiate a suitable {@link SubtitleParser}. False otherwise. * @return True if the factory can instantiate a suitable
* {@link Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException>}. False
* otherwise.
*/ */
boolean supportsFormat(Format format); boolean supportsFormat(Format format);
/** /**
* Creates a {@link SubtitleParser} for the given {@link Format}. * Creates a {@link Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException>} for
* the given {@link Format}.
* *
* @param format The {@link Format}. * @param format The {@link Format}.
* @return A new {@link SubtitleParser}. * @return A new {@link Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException>}.
* @throws IllegalArgumentException If the {@link Format} is not supported. * @throws IllegalArgumentException If the {@link Format} is not supported.
*/ */
SubtitleParser createParser(Format format); Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException> createParser(Format format);
/** /**
* Default {@link SubtitleParserFactory} implementation. * Default {@link SubtitleParserFactory} implementation.
...@@ -51,6 +58,7 @@ public interface SubtitleParserFactory { ...@@ -51,6 +58,7 @@ public interface SubtitleParserFactory {
* <li>TTML ({@link com.google.android.exoplayer.text.ttml.TtmlParser})</li> * <li>TTML ({@link com.google.android.exoplayer.text.ttml.TtmlParser})</li>
* <li>SubRip ({@link com.google.android.exoplayer.text.subrip.SubripParser})</li> * <li>SubRip ({@link com.google.android.exoplayer.text.subrip.SubripParser})</li>
* <li>TX3G ({@link com.google.android.exoplayer.text.tx3g.Tx3gParser})</li> * <li>TX3G ({@link com.google.android.exoplayer.text.tx3g.Tx3gParser})</li>
* <li>Eia608 ({@link com.google.android.exoplayer.text.eia608.Eia608Parser})</li>
* </ul> * </ul>
*/ */
SubtitleParserFactory DEFAULT = new SubtitleParserFactory() { SubtitleParserFactory DEFAULT = new SubtitleParserFactory() {
...@@ -61,13 +69,14 @@ public interface SubtitleParserFactory { ...@@ -61,13 +69,14 @@ public interface SubtitleParserFactory {
} }
@Override @Override
public SubtitleParser createParser(Format format) { public Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException> createParser(
Format format) {
try { try {
Class<?> clazz = getParserClass(format.sampleMimeType); Class<?> clazz = getParserClass(format.sampleMimeType);
if (clazz == null) { if (clazz == null) {
throw new IllegalArgumentException("Attempted to create parser for unsupported format"); throw new IllegalArgumentException("Attempted to create parser for unsupported format");
} }
return clazz.asSubclass(SubtitleParser.class).newInstance(); return clazz.asSubclass(Decoder.class).newInstance();
} catch (Exception e) { } catch (Exception e) {
throw new IllegalStateException("Unexpected error instantiating parser", e); throw new IllegalStateException("Unexpected error instantiating parser", e);
} }
...@@ -86,6 +95,8 @@ public interface SubtitleParserFactory { ...@@ -86,6 +95,8 @@ public interface SubtitleParserFactory {
return Class.forName("com.google.android.exoplayer.text.subrip.SubripParser"); return Class.forName("com.google.android.exoplayer.text.subrip.SubripParser");
case MimeTypes.APPLICATION_TX3G: case MimeTypes.APPLICATION_TX3G:
return Class.forName("com.google.android.exoplayer.text.tx3g.Tx3gParser"); return Class.forName("com.google.android.exoplayer.text.tx3g.Tx3gParser");
case MimeTypes.APPLICATION_EIA608:
return Class.forName("com.google.android.exoplayer.text.eia608.Eia608Parser");
default: default:
return null; return null;
} }
......
...@@ -24,6 +24,7 @@ import com.google.android.exoplayer.TrackRenderer; ...@@ -24,6 +24,7 @@ import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.TrackStream; import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.extensions.Decoder;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.os.Handler; import android.os.Handler;
...@@ -38,8 +39,9 @@ import java.util.List; ...@@ -38,8 +39,9 @@ import java.util.List;
/** /**
* A {@link TrackRenderer} for subtitles. * A {@link TrackRenderer} for subtitles.
* <p> * <p>
* Text is parsed from sample data using {@link SubtitleParser} instances obtained from a * Text is parsed from sample data using
* {@link SubtitleParserFactory}. The actual rendering of each line of text is delegated to a * {@link Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException>} instances obtained
* from a {@link SubtitleParserFactory}. The actual rendering of each line of text is delegated to a
* {@link TextRenderer}. * {@link TextRenderer}.
*/ */
@TargetApi(16) @TargetApi(16)
...@@ -54,7 +56,7 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback { ...@@ -54,7 +56,7 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback {
private boolean inputStreamEnded; private boolean inputStreamEnded;
private boolean outputStreamEnded; private boolean outputStreamEnded;
private SubtitleParser parser; private Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException> parser;
private SubtitleInputBuffer nextInputBuffer; private SubtitleInputBuffer nextInputBuffer;
private SubtitleOutputBuffer subtitle; private SubtitleOutputBuffer subtitle;
private SubtitleOutputBuffer nextSubtitle; private SubtitleOutputBuffer nextSubtitle;
...@@ -79,7 +81,8 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback { ...@@ -79,7 +81,8 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback {
* normally be the looper associated with the application's main thread, which can be obtained * normally be the looper associated with the application's main thread, which can be obtained
* using {@link android.app.Activity#getMainLooper()}. Null may be passed if the renderer * using {@link android.app.Activity#getMainLooper()}. Null may be passed if the renderer
* should be invoked directly on the player's internal rendering thread. * should be invoked directly on the player's internal rendering thread.
* @param parserFactory A factory from which to obtain {@link SubtitleParser} instances. * @param parserFactory A factory from which to obtain
* {@link Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException>} instances.
*/ */
public TextTrackRenderer(TextRenderer textRenderer, Looper textRendererLooper, public TextTrackRenderer(TextRenderer textRenderer, Looper textRendererLooper,
SubtitleParserFactory parserFactory) { SubtitleParserFactory parserFactory) {
...@@ -101,7 +104,6 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback { ...@@ -101,7 +104,6 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback {
protected void onEnabled(Format[] formats, boolean joining) throws ExoPlaybackException { protected void onEnabled(Format[] formats, boolean joining) throws ExoPlaybackException {
super.onEnabled(formats, joining); super.onEnabled(formats, joining);
parser = parserFactory.createParser(formats[0]); parser = parserFactory.createParser(formats[0]);
parser.start();
} }
@Override @Override
...@@ -174,7 +176,7 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback { ...@@ -174,7 +176,7 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback {
} }
try { try {
if (!inputStreamEnded && nextSubtitle == null) { while (!inputStreamEnded) {
if (nextInputBuffer == null) { if (nextInputBuffer == null) {
nextInputBuffer = parser.dequeueInputBuffer(); nextInputBuffer = parser.dequeueInputBuffer();
if (nextInputBuffer == null) { if (nextInputBuffer == null) {
...@@ -193,6 +195,8 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback { ...@@ -193,6 +195,8 @@ public final class TextTrackRenderer extends TrackRenderer implements Callback {
} }
parser.queueInputBuffer(nextInputBuffer); parser.queueInputBuffer(nextInputBuffer);
nextInputBuffer = null; nextInputBuffer = null;
} else if (result == TrackStream.NOTHING_READ) {
break;
} }
} }
} catch (ParserException e) { } catch (ParserException e) {
......
...@@ -15,25 +15,14 @@ ...@@ -15,25 +15,14 @@
*/ */
package com.google.android.exoplayer.text.eia608; package com.google.android.exoplayer.text.eia608;
/* package */ final class ClosedCaptionList implements Comparable<ClosedCaptionList> { /* package */ final class ClosedCaptionList {
public final long timeUs; public final long timeUs;
public final boolean decodeOnly;
public final ClosedCaption[] captions; public final ClosedCaption[] captions;
public ClosedCaptionList(long timeUs, boolean decodeOnly, ClosedCaption[] captions) { public ClosedCaptionList(long timeUs, ClosedCaption[] captions) {
this.timeUs = timeUs; this.timeUs = timeUs;
this.decodeOnly = decodeOnly;
this.captions = captions; this.captions = captions;
} }
@Override
public int compareTo(ClosedCaptionList other) {
long delta = timeUs - other.timeUs;
if (delta == 0) {
return 0;
}
return delta > 0 ? 1 : -1;
}
} }
...@@ -15,18 +15,28 @@ ...@@ -15,18 +15,28 @@
*/ */
package com.google.android.exoplayer.text.eia608; package com.google.android.exoplayer.text.eia608;
import com.google.android.exoplayer.DecoderInputBuffer; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.util.MimeTypes; 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.util.Assertions;
import com.google.android.exoplayer.util.ParsableBitArray; import com.google.android.exoplayer.util.ParsableBitArray;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import com.google.android.exoplayer.util.extensions.Decoder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedList;
import java.util.TreeSet;
/** /**
* Facilitates the extraction and parsing of EIA-608 (a.k.a. "line 21 captions" and "CEA-608") * 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. * Closed Captions from the SEI data block from H.264.
*/ */
public final class Eia608Parser { public final class Eia608Parser implements
Decoder<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException> {
private static final int NUM_INPUT_BUFFERS = 10;
private static final int NUM_OUTPUT_BUFFERS = 2;
private static final int PAYLOAD_TYPE_CC = 4; private static final int PAYLOAD_TYPE_CC = 4;
private static final int COUNTRY_CODE = 0xB5; private static final int COUNTRY_CODE = 0xB5;
...@@ -34,6 +44,14 @@ public final class Eia608Parser { ...@@ -34,6 +44,14 @@ public final class Eia608Parser {
private static final int USER_ID = 0x47413934; // "GA94" private static final int USER_ID = 0x47413934; // "GA94"
private static final int USER_DATA_TYPE_CODE = 0x3; private static final int USER_DATA_TYPE_CODE = 0x3;
private static final int CC_MODE_UNKNOWN = 0;
private static final int CC_MODE_ROLL_UP = 1;
private static final int CC_MODE_POP_ON = 2;
private static final int CC_MODE_PAINT_ON = 3;
// The default number of rows to display in roll-up captions mode.
private static final int DEFAULT_CAPTIONS_ROW_COUNT = 4;
// Basic North American 608 CC char set, mostly ASCII. Indexed by (char-0x20). // Basic North American 608 CC char set, mostly ASCII. Indexed by (char-0x20).
private static final int[] BASIC_CHARACTER_SET = new int[] { private static final int[] BASIC_CHARACTER_SET = new int[] {
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, // ! " # $ % & ' 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, // ! " # $ % & '
...@@ -102,28 +120,126 @@ public final class Eia608Parser { ...@@ -102,28 +120,126 @@ public final class Eia608Parser {
0xC5, 0xE5, 0xD8, 0xF8, 0x250C, 0x2510, 0x2514, 0x2518 0xC5, 0xE5, 0xD8, 0xF8, 0x250C, 0x2510, 0x2514, 0x2518
}; };
private final LinkedList<SubtitleInputBuffer> availableInputBuffers;
private final LinkedList<SubtitleOutputBuffer> availableOutputBuffers;
private final TreeSet<SubtitleInputBuffer> queuedInputBuffers;
private final ParsableBitArray seiBuffer; private final ParsableBitArray seiBuffer;
private final StringBuilder stringBuilder; private final StringBuilder textStringBuilder;
private final ArrayList<ClosedCaption> captions; private final ArrayList<ClosedCaption> captions;
/* package */ Eia608Parser() { private final StringBuilder captionStringBuilder;
private SubtitleInputBuffer dequeuedInputBuffer;
private int captionMode;
private int captionRowCount;
private String captionString;
private ClosedCaptionCtrl repeatableControl;
public Eia608Parser() {
availableInputBuffers = new LinkedList<>();
for (int i = 0; i < NUM_INPUT_BUFFERS; i++) {
availableInputBuffers.add(new SubtitleInputBuffer());
}
availableOutputBuffers = new LinkedList<>();
for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) {
availableOutputBuffers.add(new Eia608SubtitleOutputBuffer(this));
}
queuedInputBuffers = new TreeSet<>();
seiBuffer = new ParsableBitArray(); seiBuffer = new ParsableBitArray();
stringBuilder = new StringBuilder(); textStringBuilder = new StringBuilder();
captions = new ArrayList<>(); captions = new ArrayList<>();
captionStringBuilder = new StringBuilder();
setCaptionMode(CC_MODE_UNKNOWN);
captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT;
} }
/* package */ boolean canParse(String mimeType) { @Override
return mimeType.equals(MimeTypes.APPLICATION_EIA608); public SubtitleInputBuffer dequeueInputBuffer() throws ParserException {
Assertions.checkState(dequeuedInputBuffer == null);
if (availableInputBuffers.isEmpty()) {
return null;
}
dequeuedInputBuffer = availableInputBuffers.pollFirst();
return dequeuedInputBuffer;
} }
/* package */ ClosedCaptionList parse(DecoderInputBuffer buffer) { @Override
if (buffer.size < 10) { public void queueInputBuffer(SubtitleInputBuffer inputBuffer) throws ParserException {
Assertions.checkArgument(inputBuffer != null);
Assertions.checkArgument(inputBuffer == dequeuedInputBuffer);
queuedInputBuffers.add(inputBuffer);
dequeuedInputBuffer = null;
}
@Override
public SubtitleOutputBuffer dequeueOutputBuffer() throws ParserException {
if (queuedInputBuffers.isEmpty() || availableOutputBuffers.isEmpty()) {
return null;
}
SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst();
SubtitleInputBuffer inputBuffer = queuedInputBuffers.pollFirst();
// TODO: investigate ways of batching multiple SubtitleInputBuffers into a single
// SubtitleOutputBuffer
if (inputBuffer.isEndOfStream()) {
outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
return outputBuffer;
}
ClosedCaptionList captionList = decode(inputBuffer);
Eia608Subtitle subtitle = generateSubtitle(captionList);
outputBuffer.setOutput(inputBuffer.timeUs, subtitle, 0);
if (inputBuffer.isDecodeOnly()) {
outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
}
releaseInputBuffer(inputBuffer);
return outputBuffer;
}
private void releaseInputBuffer(SubtitleInputBuffer inputBuffer) {
inputBuffer.clear();
availableInputBuffers.add(inputBuffer);
}
protected void releaseOutputBuffer(SubtitleOutputBuffer outputBuffer) {
outputBuffer.clear();
availableOutputBuffers.add(outputBuffer);
}
@Override
public void flush() {
setCaptionMode(CC_MODE_UNKNOWN);
captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT;
captionStringBuilder.setLength(0);
captionString = null;
repeatableControl = null;
while (!queuedInputBuffers.isEmpty()) {
releaseInputBuffer(queuedInputBuffers.pollFirst());
}
if (dequeuedInputBuffer != null) {
releaseInputBuffer(dequeuedInputBuffer);
dequeuedInputBuffer = null;
}
}
@Override
public void release() {
// Do nothing
}
private ClosedCaptionList decode(SubtitleInputBuffer inputBuffer) {
if (inputBuffer.size < 10) {
return null; return null;
} }
captions.clear(); captions.clear();
stringBuilder.setLength(0); textStringBuilder.setLength(0);
seiBuffer.reset(buffer.data.array()); seiBuffer.reset(inputBuffer.data.array());
// country_code (8) + provider_code (16) + user_identifier (32) + user_data_type_code (8) + // country_code (8) + provider_code (16) + user_identifier (32) + user_data_type_code (8) +
// reserved (1) + process_cc_data_flag (1) + zero_bit (1) // reserved (1) + process_cc_data_flag (1) + zero_bit (1)
...@@ -157,7 +273,7 @@ public final class Eia608Parser { ...@@ -157,7 +273,7 @@ public final class Eia608Parser {
// ccData2 - P|0|1|1|X|X|X|X // ccData2 - P|0|1|1|X|X|X|X
if ((ccData1 == 0x11 || ccData1 == 0x19) if ((ccData1 == 0x11 || ccData1 == 0x19)
&& ((ccData2 & 0x70) == 0x30)) { && ((ccData2 & 0x70) == 0x30)) {
stringBuilder.append(getSpecialChar(ccData2)); textStringBuilder.append(getSpecialChar(ccData2));
continue; continue;
} }
...@@ -166,7 +282,7 @@ public final class Eia608Parser { ...@@ -166,7 +282,7 @@ public final class Eia608Parser {
if ((ccData1 == 0x12 || ccData1 == 0x1A) if ((ccData1 == 0x12 || ccData1 == 0x1A)
&& ((ccData2 & 0x60) == 0x20)) { && ((ccData2 & 0x60) == 0x20)) {
backspace(); // Remove standard equivalent of the special extended char. backspace(); // Remove standard equivalent of the special extended char.
stringBuilder.append(getExtendedEsFrChar(ccData2)); textStringBuilder.append(getExtendedEsFrChar(ccData2));
continue; continue;
} }
...@@ -175,7 +291,7 @@ public final class Eia608Parser { ...@@ -175,7 +291,7 @@ public final class Eia608Parser {
if ((ccData1 == 0x13 || ccData1 == 0x1B) if ((ccData1 == 0x13 || ccData1 == 0x1B)
&& ((ccData2 & 0x60) == 0x20)) { && ((ccData2 & 0x60) == 0x20)) {
backspace(); // Remove standard equivalent of the special extended char. backspace(); // Remove standard equivalent of the special extended char.
stringBuilder.append(getExtendedPtDeChar(ccData2)); textStringBuilder.append(getExtendedPtDeChar(ccData2));
continue; continue;
} }
...@@ -186,9 +302,9 @@ public final class Eia608Parser { ...@@ -186,9 +302,9 @@ public final class Eia608Parser {
} }
// Basic North American character set. // Basic North American character set.
stringBuilder.append(getChar(ccData1)); textStringBuilder.append(getChar(ccData1));
if (ccData2 >= 0x20) { if (ccData2 >= 0x20) {
stringBuilder.append(getChar(ccData2)); textStringBuilder.append(getChar(ccData2));
} }
} }
...@@ -200,7 +316,153 @@ public final class Eia608Parser { ...@@ -200,7 +316,153 @@ public final class Eia608Parser {
ClosedCaption[] captionArray = new ClosedCaption[captions.size()]; ClosedCaption[] captionArray = new ClosedCaption[captions.size()];
captions.toArray(captionArray); captions.toArray(captionArray);
return new ClosedCaptionList(buffer.timeUs, buffer.isDecodeOnly(), captionArray); return new ClosedCaptionList(inputBuffer.timeUs, captionArray);
}
public Eia608Subtitle generateSubtitle(ClosedCaptionList captionList) {
int captionBufferSize = (captionList == null) ? 0 : captionList.captions.length;
if (captionBufferSize != 0) {
boolean isRepeatableControl = false;
for (ClosedCaption caption : captionList.captions) {
if (caption.type == ClosedCaption.TYPE_CTRL) {
ClosedCaptionCtrl captionCtrl = (ClosedCaptionCtrl) caption;
isRepeatableControl = captionBufferSize == 1 && captionCtrl.isRepeatable();
if (isRepeatableControl && repeatableControl != null
&& repeatableControl.cc1 == captionCtrl.cc1
&& repeatableControl.cc2 == captionCtrl.cc2) {
repeatableControl = null;
continue;
} else if (isRepeatableControl) {
repeatableControl = captionCtrl;
}
if (captionCtrl.isMiscCode()) {
handleMiscCode(captionCtrl);
} else if (captionCtrl.isPreambleAddressCode()) {
handlePreambleAddressCode();
}
} else {
handleText((ClosedCaptionText) caption);
}
}
if (!isRepeatableControl) {
repeatableControl = null;
}
if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) {
captionString = getDisplayCaption();
}
}
return new Eia608Subtitle(captionString);
}
private void handleMiscCode(ClosedCaptionCtrl caption) {
switch (caption.cc2) {
case ClosedCaptionCtrl.ROLL_UP_CAPTIONS_2_ROWS:
captionRowCount = 2;
setCaptionMode(CC_MODE_ROLL_UP);
return;
case ClosedCaptionCtrl.ROLL_UP_CAPTIONS_3_ROWS:
captionRowCount = 3;
setCaptionMode(CC_MODE_ROLL_UP);
return;
case ClosedCaptionCtrl.ROLL_UP_CAPTIONS_4_ROWS:
captionRowCount = 4;
setCaptionMode(CC_MODE_ROLL_UP);
return;
case ClosedCaptionCtrl.RESUME_CAPTION_LOADING:
setCaptionMode(CC_MODE_POP_ON);
return;
case ClosedCaptionCtrl.RESUME_DIRECT_CAPTIONING:
setCaptionMode(CC_MODE_PAINT_ON);
return;
}
if (captionMode == CC_MODE_UNKNOWN) {
return;
}
switch (caption.cc2) {
case ClosedCaptionCtrl.ERASE_DISPLAYED_MEMORY:
captionString = null;
if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) {
captionStringBuilder.setLength(0);
}
return;
case ClosedCaptionCtrl.ERASE_NON_DISPLAYED_MEMORY:
captionStringBuilder.setLength(0);
return;
case ClosedCaptionCtrl.END_OF_CAPTION:
captionString = getDisplayCaption();
captionStringBuilder.setLength(0);
return;
case ClosedCaptionCtrl.CARRIAGE_RETURN:
maybeAppendNewline();
return;
case ClosedCaptionCtrl.BACKSPACE:
if (captionStringBuilder.length() > 0) {
captionStringBuilder.setLength(captionStringBuilder.length() - 1);
}
return;
}
}
private void handlePreambleAddressCode() {
// TODO: Add better handling of this with specific positioning.
maybeAppendNewline();
}
private void handleText(ClosedCaptionText caption) {
captionStringBuilder.append(caption.text);
}
private void maybeAppendNewline() {
int buildLength = captionStringBuilder.length();
if (buildLength > 0 && captionStringBuilder.charAt(buildLength - 1) != '\n') {
captionStringBuilder.append('\n');
}
}
private String getDisplayCaption() {
int buildLength = captionStringBuilder.length();
if (buildLength == 0) {
return null;
}
boolean endsWithNewline = captionStringBuilder.charAt(buildLength - 1) == '\n';
if (buildLength == 1 && endsWithNewline) {
return null;
}
int endIndex = endsWithNewline ? buildLength - 1 : buildLength;
if (captionMode != CC_MODE_ROLL_UP) {
return captionStringBuilder.substring(0, endIndex);
}
int startIndex = 0;
int searchBackwardFromIndex = endIndex;
for (int i = 0; i < captionRowCount && searchBackwardFromIndex != -1; i++) {
searchBackwardFromIndex = captionStringBuilder.lastIndexOf("\n", searchBackwardFromIndex - 1);
}
if (searchBackwardFromIndex != -1) {
startIndex = searchBackwardFromIndex + 1;
}
captionStringBuilder.delete(0, startIndex);
return captionStringBuilder.substring(0, endIndex - startIndex);
}
private void setCaptionMode(int captionMode) {
if (this.captionMode == captionMode) {
return;
}
this.captionMode = captionMode;
// Clear the working memory.
captionStringBuilder.setLength(0);
if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_UNKNOWN) {
// When switching to roll-up or unknown, we also need to clear the caption.
captionString = null;
}
} }
private static char getChar(byte ccData) { private static char getChar(byte ccData) {
...@@ -224,9 +486,10 @@ public final class Eia608Parser { ...@@ -224,9 +486,10 @@ public final class Eia608Parser {
} }
private void addBufferedText() { private void addBufferedText() {
if (stringBuilder.length() > 0) { if (textStringBuilder.length() > 0) {
captions.add(new ClosedCaptionText(stringBuilder.toString())); String textSnippet = textStringBuilder.toString();
stringBuilder.setLength(0); captions.add(new ClosedCaptionText(textSnippet));
textStringBuilder.setLength(0);
} }
} }
......
/*
* 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.eia608;
import com.google.android.exoplayer.text.Cue;
import com.google.android.exoplayer.text.Subtitle;
import java.util.Collections;
import java.util.List;
/**
* A representation of an EIA-608 subtitle.
*/
public final class Eia608Subtitle implements Subtitle {
private final String caption;
public Eia608Subtitle(String caption) {
this.caption = caption;
}
@Override
public int getNextEventTimeIndex(long timeUs) {
return 0;
}
@Override
public int getEventTimeCount() {
return 1;
}
@Override
public long getEventTime(int index) {
return 0;
}
@Override
public List<Cue> getCues(long timeUs) {
if (caption == null || caption.isEmpty()) {
return Collections.<Cue>emptyList();
} else {
return Collections.singletonList(new Cue(caption));
}
}
}
/*
* 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.eia608;
import com.google.android.exoplayer.text.Subtitle;
import com.google.android.exoplayer.text.SubtitleOutputBuffer;
/**
* A {@link Subtitle} output from an {@link Eia608Parser}.
*/
public final class Eia608SubtitleOutputBuffer extends SubtitleOutputBuffer {
private Eia608Parser owner;
public Eia608SubtitleOutputBuffer(Eia608Parser owner) {
super();
this.owner = owner;
}
@Override
public final void release() {
owner.releaseOutputBuffer(this);
}
}
/*
* 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.eia608;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.DecoderInputBuffer;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.FormatHolder;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.text.Cue;
import com.google.android.exoplayer.text.TextRenderer;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Util;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.Looper;
import android.os.Message;
import java.util.Collections;
import java.util.TreeSet;
/**
* A {@link TrackRenderer} for EIA-608 closed captions in a media stream.
*/
public final class Eia608TrackRenderer extends TrackRenderer implements Callback {
private static final int MSG_INVOKE_RENDERER = 0;
private static final int CC_MODE_UNKNOWN = 0;
private static final int CC_MODE_ROLL_UP = 1;
private static final int CC_MODE_POP_ON = 2;
private static final int CC_MODE_PAINT_ON = 3;
// The default number of rows to display in roll-up captions mode.
private static final int DEFAULT_CAPTIONS_ROW_COUNT = 4;
// The maximum duration that captions are parsed ahead of the current position.
private static final int MAX_BUFFER_READAHEAD_US = 5000000;
private final Eia608Parser eia608Parser;
private final TextRenderer textRenderer;
private final Handler textRendererHandler;
private final FormatHolder formatHolder;
private final DecoderInputBuffer buffer;
private final StringBuilder captionStringBuilder;
private final TreeSet<ClosedCaptionList> pendingCaptionLists;
private boolean inputStreamEnded;
private int captionMode;
private int captionRowCount;
private String caption;
private String lastRenderedCaption;
private ClosedCaptionCtrl repeatableControl;
/**
* @param textRenderer The text renderer.
* @param textRendererLooper The looper associated with the thread on which textRenderer should be
* invoked. If the renderer makes use of standard Android UI components, then this should
* normally be the looper associated with the applications' main thread, which can be
* obtained using {@link android.app.Activity#getMainLooper()}. Null may be passed if the
* renderer should be invoked directly on the player's internal rendering thread.
*/
public Eia608TrackRenderer(TextRenderer textRenderer, Looper textRendererLooper) {
this.textRenderer = Assertions.checkNotNull(textRenderer);
textRendererHandler = textRendererLooper == null ? null : new Handler(textRendererLooper, this);
eia608Parser = new Eia608Parser();
formatHolder = new FormatHolder();
buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
captionStringBuilder = new StringBuilder();
pendingCaptionLists = new TreeSet<>();
}
@Override
protected int supportsFormat(Format format) {
return eia608Parser.canParse(format.sampleMimeType) ? TrackRenderer.FORMAT_HANDLED
: TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
}
@Override
protected void reset(long positionUs) {
inputStreamEnded = false;
repeatableControl = null;
pendingCaptionLists.clear();
clearPendingBuffer();
captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT;
setCaptionMode(CC_MODE_UNKNOWN);
invokeRenderer(null);
}
@Override
protected void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
if (isBufferPending()) {
maybeParsePendingBuffer(positionUs);
}
while (!isBufferPending() && !inputStreamEnded) {
int result = readSource(formatHolder, buffer);
if (result == TrackStream.BUFFER_READ) {
if (buffer.isEndOfStream()) {
inputStreamEnded = true;
} else {
maybeParsePendingBuffer(positionUs);
}
} else if (result == TrackStream.NOTHING_READ) {
break;
}
}
while (!pendingCaptionLists.isEmpty()) {
if (pendingCaptionLists.first().timeUs > positionUs) {
// We're too early to render any of the pending caption lists.
return;
}
// Remove and consume the next caption list.
ClosedCaptionList nextCaptionList = pendingCaptionLists.pollFirst();
consumeCaptionList(nextCaptionList);
// Update the renderer, unless the caption list was marked for decoding only.
if (!nextCaptionList.decodeOnly) {
invokeRenderer(caption);
}
}
}
@Override
protected boolean isEnded() {
return inputStreamEnded;
}
@Override
protected boolean isReady() {
return true;
}
private void invokeRenderer(String text) {
if (Util.areEqual(lastRenderedCaption, text)) {
// No change.
return;
}
this.lastRenderedCaption = text;
if (textRendererHandler != null) {
textRendererHandler.obtainMessage(MSG_INVOKE_RENDERER, text).sendToTarget();
} else {
invokeRendererInternal(text);
}
}
@SuppressWarnings("unchecked")
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVOKE_RENDERER:
invokeRendererInternal((String) msg.obj);
return true;
}
return false;
}
private void invokeRendererInternal(String cueText) {
if (cueText == null) {
textRenderer.onCues(Collections.<Cue>emptyList());
} else {
textRenderer.onCues(Collections.singletonList(new Cue(cueText)));
}
}
private void maybeParsePendingBuffer(long positionUs) {
if (buffer.timeUs > positionUs + MAX_BUFFER_READAHEAD_US) {
// We're too early to parse the buffer.
return;
}
ClosedCaptionList holder = eia608Parser.parse(buffer);
clearPendingBuffer();
if (holder != null) {
pendingCaptionLists.add(holder);
}
}
private void consumeCaptionList(ClosedCaptionList captionList) {
int captionBufferSize = captionList.captions.length;
if (captionBufferSize == 0) {
return;
}
boolean isRepeatableControl = false;
for (int i = 0; i < captionBufferSize; i++) {
ClosedCaption caption = captionList.captions[i];
if (caption.type == ClosedCaption.TYPE_CTRL) {
ClosedCaptionCtrl captionCtrl = (ClosedCaptionCtrl) caption;
isRepeatableControl = captionBufferSize == 1 && captionCtrl.isRepeatable();
if (isRepeatableControl && repeatableControl != null
&& repeatableControl.cc1 == captionCtrl.cc1
&& repeatableControl.cc2 == captionCtrl.cc2) {
repeatableControl = null;
continue;
} else if (isRepeatableControl) {
repeatableControl = captionCtrl;
}
if (captionCtrl.isMiscCode()) {
handleMiscCode(captionCtrl);
} else if (captionCtrl.isPreambleAddressCode()) {
handlePreambleAddressCode();
}
} else {
handleText((ClosedCaptionText) caption);
}
}
if (!isRepeatableControl) {
repeatableControl = null;
}
if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) {
caption = getDisplayCaption();
}
}
private void handleText(ClosedCaptionText captionText) {
if (captionMode != CC_MODE_UNKNOWN) {
captionStringBuilder.append(captionText.text);
}
}
private void handleMiscCode(ClosedCaptionCtrl captionCtrl) {
switch (captionCtrl.cc2) {
case ClosedCaptionCtrl.ROLL_UP_CAPTIONS_2_ROWS:
captionRowCount = 2;
setCaptionMode(CC_MODE_ROLL_UP);
return;
case ClosedCaptionCtrl.ROLL_UP_CAPTIONS_3_ROWS:
captionRowCount = 3;
setCaptionMode(CC_MODE_ROLL_UP);
return;
case ClosedCaptionCtrl.ROLL_UP_CAPTIONS_4_ROWS:
captionRowCount = 4;
setCaptionMode(CC_MODE_ROLL_UP);
return;
case ClosedCaptionCtrl.RESUME_CAPTION_LOADING:
setCaptionMode(CC_MODE_POP_ON);
return;
case ClosedCaptionCtrl.RESUME_DIRECT_CAPTIONING:
setCaptionMode(CC_MODE_PAINT_ON);
return;
}
if (captionMode == CC_MODE_UNKNOWN) {
return;
}
switch (captionCtrl.cc2) {
case ClosedCaptionCtrl.ERASE_DISPLAYED_MEMORY:
caption = null;
if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) {
captionStringBuilder.setLength(0);
}
return;
case ClosedCaptionCtrl.ERASE_NON_DISPLAYED_MEMORY:
captionStringBuilder.setLength(0);
return;
case ClosedCaptionCtrl.END_OF_CAPTION:
caption = getDisplayCaption();
captionStringBuilder.setLength(0);
return;
case ClosedCaptionCtrl.CARRIAGE_RETURN:
maybeAppendNewline();
return;
case ClosedCaptionCtrl.BACKSPACE:
if (captionStringBuilder.length() > 0) {
captionStringBuilder.setLength(captionStringBuilder.length() - 1);
}
return;
}
}
private void handlePreambleAddressCode() {
// TODO: Add better handling of this with specific positioning.
maybeAppendNewline();
}
private void setCaptionMode(int captionMode) {
if (this.captionMode == captionMode) {
return;
}
this.captionMode = captionMode;
// Clear the working memory.
captionStringBuilder.setLength(0);
if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_UNKNOWN) {
// When switching to roll-up or unknown, we also need to clear the caption.
caption = null;
}
}
private void maybeAppendNewline() {
int buildLength = captionStringBuilder.length();
if (buildLength > 0 && captionStringBuilder.charAt(buildLength - 1) != '\n') {
captionStringBuilder.append('\n');
}
}
private String getDisplayCaption() {
int buildLength = captionStringBuilder.length();
if (buildLength == 0) {
return null;
}
boolean endsWithNewline = captionStringBuilder.charAt(buildLength - 1) == '\n';
if (buildLength == 1 && endsWithNewline) {
return null;
}
int endIndex = endsWithNewline ? buildLength - 1 : buildLength;
if (captionMode != CC_MODE_ROLL_UP) {
return captionStringBuilder.substring(0, endIndex);
}
int startIndex = 0;
int searchBackwardFromIndex = endIndex;
for (int i = 0; i < captionRowCount && searchBackwardFromIndex != -1; i++) {
searchBackwardFromIndex = captionStringBuilder.lastIndexOf("\n", searchBackwardFromIndex - 1);
}
if (searchBackwardFromIndex != -1) {
startIndex = searchBackwardFromIndex + 1;
}
captionStringBuilder.delete(0, startIndex);
return captionStringBuilder.substring(0, endIndex - startIndex);
}
private void clearPendingBuffer() {
buffer.clear();
buffer.timeUs = C.UNSET_TIME_US;
}
private boolean isBufferPending() {
return buffer.timeUs != C.UNSET_TIME_US;
}
}
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
package com.google.android.exoplayer.text.subrip; package com.google.android.exoplayer.text.subrip;
import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.Cue;
import com.google.android.exoplayer.text.SubtitleParser; import com.google.android.exoplayer.text.SimpleSubtitleParser;
import com.google.android.exoplayer.util.LongArray; import com.google.android.exoplayer.util.LongArray;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
...@@ -32,7 +32,7 @@ import java.util.regex.Pattern; ...@@ -32,7 +32,7 @@ import java.util.regex.Pattern;
/** /**
* A simple SubRip parser. * A simple SubRip parser.
*/ */
public final class SubripParser extends SubtitleParser { public final class SubripParser extends SimpleSubtitleParser {
private static final String TAG = "SubripParser"; private static final String TAG = "SubripParser";
......
...@@ -17,7 +17,7 @@ package com.google.android.exoplayer.text.ttml; ...@@ -17,7 +17,7 @@ package com.google.android.exoplayer.text.ttml;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.text.SubtitleParser; import com.google.android.exoplayer.text.SimpleSubtitleParser;
import com.google.android.exoplayer.util.ColorParser; import com.google.android.exoplayer.util.ColorParser;
import com.google.android.exoplayer.util.ParserUtil; import com.google.android.exoplayer.util.ParserUtil;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
...@@ -58,7 +58,7 @@ import java.util.regex.Pattern; ...@@ -58,7 +58,7 @@ import java.util.regex.Pattern;
* </p> * </p>
* @see <a href="http://www.w3.org/TR/ttaf1-dfxp/">TTML specification</a> * @see <a href="http://www.w3.org/TR/ttaf1-dfxp/">TTML specification</a>
*/ */
public final class TtmlParser extends SubtitleParser { public final class TtmlParser extends SimpleSubtitleParser {
private static final String TAG = "TtmlParser"; private static final String TAG = "TtmlParser";
......
...@@ -16,15 +16,15 @@ ...@@ -16,15 +16,15 @@
package com.google.android.exoplayer.text.tx3g; package com.google.android.exoplayer.text.tx3g;
import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.Cue;
import com.google.android.exoplayer.text.SimpleSubtitleParser;
import com.google.android.exoplayer.text.Subtitle; import com.google.android.exoplayer.text.Subtitle;
import com.google.android.exoplayer.text.SubtitleParser;
/** /**
* A {@link SubtitleParser} for tx3g. * A subtitle parser for tx3g.
* <p> * <p>
* Currently only supports parsing of a single text track. * Currently only supports parsing of a single text track.
*/ */
public final class Tx3gParser extends SubtitleParser { public final class Tx3gParser extends SimpleSubtitleParser {
@Override @Override
protected Subtitle decode(byte[] bytes, int length) { protected Subtitle decode(byte[] bytes, int length) {
......
...@@ -17,7 +17,7 @@ package com.google.android.exoplayer.text.webvtt; ...@@ -17,7 +17,7 @@ package com.google.android.exoplayer.text.webvtt;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.Cue;
import com.google.android.exoplayer.text.SubtitleParser; import com.google.android.exoplayer.text.SimpleSubtitleParser;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
...@@ -26,9 +26,9 @@ import java.util.Collections; ...@@ -26,9 +26,9 @@ import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
* A {@link SubtitleParser} for Webvtt embedded in a Mp4 container file. * A subtitle parser for Webvtt embedded in a Mp4 container file.
*/ */
public final class Mp4WebvttParser extends SubtitleParser { public final class Mp4WebvttParser extends SimpleSubtitleParser {
private static final int BOX_HEADER_SIZE = 8; private static final int BOX_HEADER_SIZE = 8;
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
package com.google.android.exoplayer.text.webvtt; package com.google.android.exoplayer.text.webvtt;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.text.SubtitleParser; import com.google.android.exoplayer.text.SimpleSubtitleParser;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import android.text.TextUtils; import android.text.TextUtils;
...@@ -29,7 +29,7 @@ import java.util.List; ...@@ -29,7 +29,7 @@ import java.util.List;
* <p> * <p>
* @see <a href="http://dev.w3.org/html5/webvtt">WebVTT specification</a> * @see <a href="http://dev.w3.org/html5/webvtt">WebVTT specification</a>
*/ */
public final class WebvttParser extends SubtitleParser { public final class WebvttParser extends SimpleSubtitleParser {
private static final int NO_EVENT_FOUND = -1; private static final int NO_EVENT_FOUND = -1;
private static final int END_OF_FILE_FOUND = 0; private static final int END_OF_FILE_FOUND = 0;
......
...@@ -132,7 +132,6 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements ...@@ -132,7 +132,6 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
notifyDecoderError(e); notifyDecoderError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex()); throw ExoPlaybackException.createForRenderer(e, getIndex());
} }
decoder.start();
codecCounters.codecInitCount++; codecCounters.codecInitCount++;
} }
......
...@@ -25,7 +25,7 @@ import java.util.LinkedList; ...@@ -25,7 +25,7 @@ import java.util.LinkedList;
* Base class for {@link Decoder}s that use their own decode thread. * Base class for {@link Decoder}s that use their own decode thread.
*/ */
public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends OutputBuffer, public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends OutputBuffer,
E extends Exception> extends Thread implements Decoder<I, O, E> { E extends Exception> implements Decoder<I, O, E> {
/** /**
* Listener for {@link SimpleDecoder} events. * Listener for {@link SimpleDecoder} events.
...@@ -41,6 +41,8 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp ...@@ -41,6 +41,8 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
} }
private final Thread decodeThread;
private final Object lock; private final Object lock;
private final LinkedList<I> queuedInputBuffers; private final LinkedList<I> queuedInputBuffers;
private final LinkedList<O> queuedOutputBuffers; private final LinkedList<O> queuedOutputBuffers;
...@@ -73,6 +75,13 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp ...@@ -73,6 +75,13 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
for (int i = 0; i < availableOutputBufferCount; i++) { for (int i = 0; i < availableOutputBufferCount; i++) {
availableOutputBuffers[i] = createOutputBuffer(); availableOutputBuffers[i] = createOutputBuffer();
} }
decodeThread = new Thread() {
@Override
public void run() {
SimpleDecoder.this.run();
}
};
decodeThread.start();
} }
/** /**
...@@ -159,7 +168,7 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp ...@@ -159,7 +168,7 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
lock.notify(); lock.notify();
} }
try { try {
join(); decodeThread.join();
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
...@@ -188,8 +197,7 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp ...@@ -188,8 +197,7 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
} }
} }
@Override private void run() {
public final void run() {
try { try {
while (decode()) { while (decode()) {
// Do nothing. // Do nothing.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment