Commit 156bc52c by Oliver Woodman

Clean up DVB support

- Removed the PES_STRIPPED flag. It's unnecessary. We can strip
  PES in the TS extractor instead.
- Made nearly all of the object classes in DvbParser immutable.
  Else it's non-obvious that none of this state can be mutated.
- Made a a lot of the methods in DvbParser static for the same
  reason.
- Removed unnecessary null checks, code that was never executed,
  unused fields etc.
- Add proper flushing of DvbParser, to prevent corrupt output
  following a seek.
parent 8c05b5b1
Showing with 202 additions and 164 deletions
...@@ -7,4 +7,4 @@ ...@@ -7,4 +7,4 @@
} }
-keepclassmembers class com.google.android.exoplayer2.text.dvb.DvbDecoder { -keepclassmembers class com.google.android.exoplayer2.text.dvb.DvbDecoder {
public <init>(java.util.List); public <init>(java.util.List);
} }
\ No newline at end of file
...@@ -36,7 +36,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase { ...@@ -36,7 +36,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
public void testDecodeEmpty() throws IOException { public void testDecodeEmpty() throws IOException {
SubripDecoder decoder = new SubripDecoder(); SubripDecoder decoder = new SubripDecoder();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), EMPTY_FILE); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), EMPTY_FILE);
SubripSubtitle subtitle = decoder.decode(bytes, bytes.length); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
// Assert that the subtitle is empty. // Assert that the subtitle is empty.
assertEquals(0, subtitle.getEventTimeCount()); assertEquals(0, subtitle.getEventTimeCount());
assertTrue(subtitle.getCues(0).isEmpty()); assertTrue(subtitle.getCues(0).isEmpty());
...@@ -45,7 +45,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase { ...@@ -45,7 +45,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
public void testDecodeTypical() throws IOException { public void testDecodeTypical() throws IOException {
SubripDecoder decoder = new SubripDecoder(); SubripDecoder decoder = new SubripDecoder();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_FILE); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_FILE);
SubripSubtitle subtitle = decoder.decode(bytes, bytes.length); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
assertEquals(6, subtitle.getEventTimeCount()); assertEquals(6, subtitle.getEventTimeCount());
assertTypicalCue1(subtitle, 0); assertTypicalCue1(subtitle, 0);
assertTypicalCue2(subtitle, 2); assertTypicalCue2(subtitle, 2);
...@@ -55,7 +55,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase { ...@@ -55,7 +55,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
public void testDecodeTypicalWithByteOrderMark() throws IOException { public void testDecodeTypicalWithByteOrderMark() throws IOException {
SubripDecoder decoder = new SubripDecoder(); SubripDecoder decoder = new SubripDecoder();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_WITH_BYTE_ORDER_MARK); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_WITH_BYTE_ORDER_MARK);
SubripSubtitle subtitle = decoder.decode(bytes, bytes.length); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
assertEquals(6, subtitle.getEventTimeCount()); assertEquals(6, subtitle.getEventTimeCount());
assertTypicalCue1(subtitle, 0); assertTypicalCue1(subtitle, 0);
assertTypicalCue2(subtitle, 2); assertTypicalCue2(subtitle, 2);
...@@ -65,7 +65,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase { ...@@ -65,7 +65,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
public void testDecodeTypicalExtraBlankLine() throws IOException { public void testDecodeTypicalExtraBlankLine() throws IOException {
SubripDecoder decoder = new SubripDecoder(); SubripDecoder decoder = new SubripDecoder();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_EXTRA_BLANK_LINE); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_EXTRA_BLANK_LINE);
SubripSubtitle subtitle = decoder.decode(bytes, bytes.length); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
assertEquals(6, subtitle.getEventTimeCount()); assertEquals(6, subtitle.getEventTimeCount());
assertTypicalCue1(subtitle, 0); assertTypicalCue1(subtitle, 0);
assertTypicalCue2(subtitle, 2); assertTypicalCue2(subtitle, 2);
...@@ -76,7 +76,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase { ...@@ -76,7 +76,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
// Parsing should succeed, parsing the first and third cues only. // Parsing should succeed, parsing the first and third cues only.
SubripDecoder decoder = new SubripDecoder(); SubripDecoder decoder = new SubripDecoder();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_MISSING_TIMECODE); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_MISSING_TIMECODE);
SubripSubtitle subtitle = decoder.decode(bytes, bytes.length); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
assertEquals(4, subtitle.getEventTimeCount()); assertEquals(4, subtitle.getEventTimeCount());
assertTypicalCue1(subtitle, 0); assertTypicalCue1(subtitle, 0);
assertTypicalCue3(subtitle, 2); assertTypicalCue3(subtitle, 2);
...@@ -86,7 +86,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase { ...@@ -86,7 +86,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
// Parsing should succeed, parsing the first and third cues only. // Parsing should succeed, parsing the first and third cues only.
SubripDecoder decoder = new SubripDecoder(); SubripDecoder decoder = new SubripDecoder();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_MISSING_SEQUENCE); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_MISSING_SEQUENCE);
SubripSubtitle subtitle = decoder.decode(bytes, bytes.length); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
assertEquals(4, subtitle.getEventTimeCount()); assertEquals(4, subtitle.getEventTimeCount());
assertTypicalCue1(subtitle, 0); assertTypicalCue1(subtitle, 0);
assertTypicalCue3(subtitle, 2); assertTypicalCue3(subtitle, 2);
...@@ -96,7 +96,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase { ...@@ -96,7 +96,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
// Parsing should succeed, parsing the third cue only. // Parsing should succeed, parsing the third cue only.
SubripDecoder decoder = new SubripDecoder(); SubripDecoder decoder = new SubripDecoder();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_NEGATIVE_TIMESTAMPS); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_NEGATIVE_TIMESTAMPS);
SubripSubtitle subtitle = decoder.decode(bytes, bytes.length); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
assertEquals(2, subtitle.getEventTimeCount()); assertEquals(2, subtitle.getEventTimeCount());
assertTypicalCue3(subtitle, 0); assertTypicalCue3(subtitle, 0);
} }
...@@ -104,7 +104,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase { ...@@ -104,7 +104,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
public void testDecodeNoEndTimecodes() throws IOException { public void testDecodeNoEndTimecodes() throws IOException {
SubripDecoder decoder = new SubripDecoder(); SubripDecoder decoder = new SubripDecoder();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), NO_END_TIMECODES_FILE); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), NO_END_TIMECODES_FILE);
SubripSubtitle subtitle = decoder.decode(bytes, bytes.length); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
// Test event count. // Test event count.
assertEquals(3, subtitle.getEventTimeCount()); assertEquals(3, subtitle.getEventTimeCount());
......
...@@ -482,7 +482,7 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { ...@@ -482,7 +482,7 @@ public final class TtmlDecoderTest extends InstrumentationTestCase {
private TtmlSubtitle getSubtitle(String file) throws IOException, SubtitleDecoderException { private TtmlSubtitle getSubtitle(String file) throws IOException, SubtitleDecoderException {
TtmlDecoder ttmlDecoder = new TtmlDecoder(); TtmlDecoder ttmlDecoder = new TtmlDecoder();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), file); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), file);
return ttmlDecoder.decode(bytes, bytes.length); return ttmlDecoder.decode(bytes, bytes.length, false);
} }
} }
...@@ -81,14 +81,14 @@ public final class Mp4WebvttDecoderTest extends TestCase { ...@@ -81,14 +81,14 @@ public final class Mp4WebvttDecoderTest extends TestCase {
public void testSingleCueSample() throws SubtitleDecoderException { public void testSingleCueSample() throws SubtitleDecoderException {
Mp4WebvttDecoder decoder = new Mp4WebvttDecoder(); Mp4WebvttDecoder decoder = new Mp4WebvttDecoder();
Subtitle result = decoder.decode(SINGLE_CUE_SAMPLE, SINGLE_CUE_SAMPLE.length); Subtitle result = decoder.decode(SINGLE_CUE_SAMPLE, SINGLE_CUE_SAMPLE.length, false);
Cue expectedCue = new Cue("Hello World"); // Line feed must be trimmed by the decoder Cue expectedCue = new Cue("Hello World"); // Line feed must be trimmed by the decoder
assertMp4WebvttSubtitleEquals(result, expectedCue); assertMp4WebvttSubtitleEquals(result, expectedCue);
} }
public void testTwoCuesSample() throws SubtitleDecoderException { public void testTwoCuesSample() throws SubtitleDecoderException {
Mp4WebvttDecoder decoder = new Mp4WebvttDecoder(); Mp4WebvttDecoder decoder = new Mp4WebvttDecoder();
Subtitle result = decoder.decode(DOUBLE_CUE_SAMPLE, DOUBLE_CUE_SAMPLE.length); Subtitle result = decoder.decode(DOUBLE_CUE_SAMPLE, DOUBLE_CUE_SAMPLE.length, false);
Cue firstExpectedCue = new Cue("Hello World"); Cue firstExpectedCue = new Cue("Hello World");
Cue secondExpectedCue = new Cue("Bye Bye"); Cue secondExpectedCue = new Cue("Bye Bye");
assertMp4WebvttSubtitleEquals(result, firstExpectedCue, secondExpectedCue); assertMp4WebvttSubtitleEquals(result, firstExpectedCue, secondExpectedCue);
...@@ -96,7 +96,7 @@ public final class Mp4WebvttDecoderTest extends TestCase { ...@@ -96,7 +96,7 @@ public final class Mp4WebvttDecoderTest extends TestCase {
public void testNoCueSample() throws SubtitleDecoderException { public void testNoCueSample() throws SubtitleDecoderException {
Mp4WebvttDecoder decoder = new Mp4WebvttDecoder(); Mp4WebvttDecoder decoder = new Mp4WebvttDecoder();
Subtitle result = decoder.decode(NO_CUE_SAMPLE, NO_CUE_SAMPLE.length); Subtitle result = decoder.decode(NO_CUE_SAMPLE, NO_CUE_SAMPLE.length, false);
assertMp4WebvttSubtitleEquals(result); assertMp4WebvttSubtitleEquals(result);
} }
...@@ -105,7 +105,7 @@ public final class Mp4WebvttDecoderTest extends TestCase { ...@@ -105,7 +105,7 @@ public final class Mp4WebvttDecoderTest extends TestCase {
public void testSampleWithIncompleteHeader() { public void testSampleWithIncompleteHeader() {
Mp4WebvttDecoder decoder = new Mp4WebvttDecoder(); Mp4WebvttDecoder decoder = new Mp4WebvttDecoder();
try { try {
decoder.decode(INCOMPLETE_HEADER_SAMPLE, INCOMPLETE_HEADER_SAMPLE.length); decoder.decode(INCOMPLETE_HEADER_SAMPLE, INCOMPLETE_HEADER_SAMPLE.length, false);
} catch (SubtitleDecoderException e) { } catch (SubtitleDecoderException e) {
return; return;
} }
......
...@@ -49,7 +49,7 @@ public class WebvttDecoderTest extends InstrumentationTestCase { ...@@ -49,7 +49,7 @@ public class WebvttDecoderTest extends InstrumentationTestCase {
WebvttDecoder decoder = new WebvttDecoder(); WebvttDecoder decoder = new WebvttDecoder();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), EMPTY_FILE); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), EMPTY_FILE);
try { try {
decoder.decode(bytes, bytes.length); decoder.decode(bytes, bytes.length, false);
fail(); fail();
} catch (SubtitleDecoderException expected) { } catch (SubtitleDecoderException expected) {
// Do nothing. // Do nothing.
...@@ -194,7 +194,7 @@ public class WebvttDecoderTest extends InstrumentationTestCase { ...@@ -194,7 +194,7 @@ public class WebvttDecoderTest extends InstrumentationTestCase {
SubtitleDecoderException { SubtitleDecoderException {
WebvttDecoder decoder = new WebvttDecoder(); WebvttDecoder decoder = new WebvttDecoder();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), asset); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), asset);
return decoder.decode(bytes, bytes.length); return decoder.decode(bytes, bytes.length, false);
} }
private Spanned getUniqueSpanTextAt(WebvttSubtitle sub, long timeUs) { private Spanned getUniqueSpanTextAt(WebvttSubtitle sub, long timeUs) {
......
...@@ -1465,8 +1465,9 @@ public final class MatroskaExtractor implements Extractor { ...@@ -1465,8 +1465,9 @@ public final class MatroskaExtractor implements Extractor {
break; break;
case CODEC_ID_DVBSUB: case CODEC_ID_DVBSUB:
mimeType = MimeTypes.APPLICATION_DVBSUBS; mimeType = MimeTypes.APPLICATION_DVBSUBS;
initializationData = Collections.singletonList(new byte[] { // Init data: composition_page (2), ancillary_page (2)
(byte) 0x01, codecPrivate[0], codecPrivate[1], codecPrivate[2], codecPrivate[3]}); initializationData = Collections.singletonList(new byte[] {codecPrivate[0],
codecPrivate[1], codecPrivate[2], codecPrivate[3]});
break; break;
default: default:
throw new ParserException("Unrecognized codec identifier."); throw new ParserException("Unrecognized codec identifier.");
......
...@@ -110,7 +110,8 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact ...@@ -110,7 +110,8 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
case TsExtractor.TS_STREAM_TYPE_ID3: case TsExtractor.TS_STREAM_TYPE_ID3:
return new PesReader(new Id3Reader()); return new PesReader(new Id3Reader());
case TsExtractor.TS_STREAM_TYPE_DVBSUBS: case TsExtractor.TS_STREAM_TYPE_DVBSUBS:
return new PesReader(new DvbSubtitlesReader(esInfo)); return new PesReader(
new DvbSubtitleReader(esInfo.language, esInfo.dvbSubtitleInitializationData));
default: default:
return null; return null;
} }
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
...@@ -23,54 +22,32 @@ import com.google.android.exoplayer2.extractor.TrackOutput; ...@@ -23,54 +22,32 @@ import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
* Output PES packets to a {@link TrackOutput}. * Parses DVB subtitle data and extracts individual frames.
*/ */
public final class DvbSubtitlesReader implements ElementaryStreamReader { public final class DvbSubtitleReader implements ElementaryStreamReader {
private class SubtitleTrack {
private String language;
private List<byte[]> initializationData;
}
private List<SubtitleTrack> subtitles = new ArrayList<>(); private final String language;
private final List<byte[]> initializationData;
private long sampleTimeUs; private TrackOutput output;
private int sampleBytesWritten;
private boolean writingSample; private boolean writingSample;
private int bytesToCheck;
private int sampleBytesWritten;
private long sampleTimeUs;
private List<TrackOutput> outputTracks = new ArrayList<>(); /**
* @param language The subtitle language code.
public DvbSubtitlesReader(TsPayloadReader.EsInfo esInfo) { * @param initializationData Initialization data to be included in the track {@link Format}.
int pos = 2; */
public DvbSubtitleReader(String language, byte[] initializationData) {
while (pos < esInfo.descriptorBytes.length) { this.language = language;
SubtitleTrack subtitle = new SubtitleTrack(); this.initializationData = Collections.singletonList(initializationData);
subtitle.language = new String(new byte[] {
esInfo.descriptorBytes[pos],
esInfo.descriptorBytes[pos + 1],
esInfo.descriptorBytes[pos + 2]});
if (((esInfo.descriptorBytes[pos + 3] & 0xF0 ) >> 4 ) == 2 ) {
subtitle.language += " for hard of hearing";
}
subtitle.initializationData = Collections.singletonList(new byte[] {(byte) 0x00,
esInfo.descriptorBytes[pos + 4], esInfo.descriptorBytes[pos + 5],
esInfo.descriptorBytes[pos + 6], esInfo.descriptorBytes[pos + 7]});
subtitles.add(subtitle);
pos += 8;
}
} }
@Override @Override
public void seek() { public void seek() {
writingSample = false; writingSample = false;
...@@ -78,21 +55,12 @@ public final class DvbSubtitlesReader implements ElementaryStreamReader { ...@@ -78,21 +55,12 @@ public final class DvbSubtitlesReader implements ElementaryStreamReader {
@Override @Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
TrackOutput output; idGenerator.generateNewId();
SubtitleTrack subtitle; this.output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
output.format(Format.createImageSampleFormat(idGenerator.getFormatId(),
for (int i = 0; i < subtitles.size(); i++) { MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE, initializationData, language, null));
subtitle = subtitles.get(i);
idGenerator.generateNewId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
output.format(Format.createImageSampleFormat(idGenerator.getFormatId(),
MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE,
subtitle.initializationData, subtitle.language, null));
outputTracks.add(output);
}
} }
@Override @Override
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
if (!dataAlignmentIndicator) { if (!dataAlignmentIndicator) {
...@@ -101,33 +69,43 @@ public final class DvbSubtitlesReader implements ElementaryStreamReader { ...@@ -101,33 +69,43 @@ public final class DvbSubtitlesReader implements ElementaryStreamReader {
writingSample = true; writingSample = true;
sampleTimeUs = pesTimeUs; sampleTimeUs = pesTimeUs;
sampleBytesWritten = 0; sampleBytesWritten = 0;
bytesToCheck = 2;
} }
@Override @Override
public void packetFinished() { public void packetFinished() {
TrackOutput output; if (writingSample) {
for (int i = 0; i < outputTracks.size(); i++) {
output = outputTracks.get(i);
output.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytesWritten, 0, null); output.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytesWritten, 0, null);
writingSample = false;
} }
writingSample = false;
} }
@Override @Override
public void consume(ParsableByteArray data) { public void consume(ParsableByteArray data) {
if (writingSample) { if (writingSample) {
int bytesAvailable = data.bytesLeft(); if (bytesToCheck == 2 && !checkNextByte(data, 0x20)) {
TrackOutput output; // Failed to check data_identifier
int dataPosition = data.getPosition(); return;
for (int i = 0; i < outputTracks.size(); i++) {
data.setPosition(dataPosition);
output = outputTracks.get(i);
output.sampleData(data, bytesAvailable);
} }
if (bytesToCheck == 1 && !checkNextByte(data, 0x00)) {
// Check and discard the subtitle_stream_id
return;
}
int bytesAvailable = data.bytesLeft();
output.sampleData(data, bytesAvailable);
sampleBytesWritten += bytesAvailable; sampleBytesWritten += bytesAvailable;
} }
} }
}
\ No newline at end of file private boolean checkNextByte(ParsableByteArray data, int expectedValue) {
if (data.bytesLeft() == 0) {
return false;
}
if (data.readUnsignedByte() != expectedValue) {
writingSample = false;
}
bytesToCheck--;
return writingSample;
}
}
...@@ -92,7 +92,7 @@ public final class TsExtractor implements Extractor { ...@@ -92,7 +92,7 @@ public final class TsExtractor implements Extractor {
public static final int TS_STREAM_TYPE_H265 = 0x24; public static final int TS_STREAM_TYPE_H265 = 0x24;
public static final int TS_STREAM_TYPE_ID3 = 0x15; public static final int TS_STREAM_TYPE_ID3 = 0x15;
public static final int TS_STREAM_TYPE_SPLICE_INFO = 0x86; public static final int TS_STREAM_TYPE_SPLICE_INFO = 0x86;
public static final int TS_STREAM_TYPE_DVBSUBS = 0x59; public static final int TS_STREAM_TYPE_DVBSUBS = 0x59;
private static final int TS_PACKET_SIZE = 188; private static final int TS_PACKET_SIZE = 188;
private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet. private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet.
...@@ -408,7 +408,7 @@ public final class TsExtractor implements Extractor { ...@@ -408,7 +408,7 @@ public final class TsExtractor implements Extractor {
if (mode == MODE_HLS && id3Reader == null) { if (mode == MODE_HLS && id3Reader == null) {
// Setup an ID3 track regardless of whether there's a corresponding entry, in case one // Setup an ID3 track regardless of whether there's a corresponding entry, in case one
// appears intermittently during playback. See [Internal: b/20261500]. // appears intermittently during playback. See [Internal: b/20261500].
EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, new byte[0]); EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, null, new byte[0]);
id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo); id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo);
id3Reader.init(timestampAdjuster, output, id3Reader.init(timestampAdjuster, output,
new TrackIdGenerator(programNumber, TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE)); new TrackIdGenerator(programNumber, TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE));
...@@ -478,6 +478,7 @@ public final class TsExtractor implements Extractor { ...@@ -478,6 +478,7 @@ public final class TsExtractor implements Extractor {
int descriptorsEndPosition = descriptorsStartPosition + length; int descriptorsEndPosition = descriptorsStartPosition + length;
int streamType = -1; int streamType = -1;
String language = null; String language = null;
byte[] dvbSubtitleInitializationData = null;
while (data.getPosition() < descriptorsEndPosition) { while (data.getPosition() < descriptorsEndPosition) {
int descriptorTag = data.readUnsignedByte(); int descriptorTag = data.readUnsignedByte();
int descriptorLength = data.readUnsignedByte(); int descriptorLength = data.readUnsignedByte();
...@@ -503,12 +504,16 @@ public final class TsExtractor implements Extractor { ...@@ -503,12 +504,16 @@ public final class TsExtractor implements Extractor {
} else if (descriptorTag == TS_PMT_DESC_DVBSUBS) { } else if (descriptorTag == TS_PMT_DESC_DVBSUBS) {
streamType = TS_STREAM_TYPE_DVBSUBS; streamType = TS_STREAM_TYPE_DVBSUBS;
language = new String(data.data, data.getPosition(), 3).trim(); language = new String(data.data, data.getPosition(), 3).trim();
data.skipBytes(4); // Skip language (3) + subtitling_type (1)
// Init data: composition_page (2), ancillary_page (2)
dvbSubtitleInitializationData = new byte[4];
data.readBytes(dvbSubtitleInitializationData, 0, 4);
} }
// Skip unused bytes of current descriptor. // Skip unused bytes of current descriptor.
data.skipBytes(positionOfNextDescriptor - data.getPosition()); data.skipBytes(positionOfNextDescriptor - data.getPosition());
} }
data.setPosition(descriptorsEndPosition); data.setPosition(descriptorsEndPosition);
return new EsInfo(streamType, language, return new EsInfo(streamType, language, dvbSubtitleInitializationData,
Arrays.copyOfRange(data.data, descriptorsStartPosition, descriptorsEndPosition)); Arrays.copyOfRange(data.data, descriptorsStartPosition, descriptorsEndPosition));
} }
......
...@@ -60,17 +60,22 @@ public interface TsPayloadReader { ...@@ -60,17 +60,22 @@ public interface TsPayloadReader {
public final int streamType; public final int streamType;
public final String language; public final String language;
public final byte[] dvbSubtitleInitializationData;
public final byte[] descriptorBytes; public final byte[] descriptorBytes;
/** /**
* @param streamType The type of the stream as defined by the * @param streamType The type of the stream as defined by the
* {@link TsExtractor}{@code .TS_STREAM_TYPE_*}. * {@link TsExtractor}{@code .TS_STREAM_TYPE_*}.
* @param language The language of the stream, as defined by ISO/IEC 13818-1, section 2.6.18. * @param language The language of the stream, as defined by ISO/IEC 13818-1, section 2.6.18.
* @param dvbSubtitleInitializationData If the descriptors include a DVB subtitle tag, this is
* the corresponding decoder initialization data. Null otherwise.
* @param descriptorBytes The descriptor bytes associated to the stream. * @param descriptorBytes The descriptor bytes associated to the stream.
*/ */
public EsInfo(int streamType, String language, byte[] descriptorBytes) { public EsInfo(int streamType, String language, byte[] dvbSubtitleInitializationData,
byte[] descriptorBytes) {
this.streamType = streamType; this.streamType = streamType;
this.language = language; this.language = language;
this.dvbSubtitleInitializationData = dvbSubtitleInitializationData;
this.descriptorBytes = descriptorBytes; this.descriptorBytes = descriptorBytes;
} }
......
...@@ -168,8 +168,9 @@ public class Cue { ...@@ -168,8 +168,9 @@ public class Cue {
public final float size; public final float size;
/** /**
* The bitmap height as a fraction of the of the viewport size, or -1 if the bitmap should be * The bitmap height as a fraction of the of the viewport size, or {@link #DIMEN_UNSET} if the
* displayed at its natural height given for its specified {@link #size}. * bitmap should be displayed at its natural height given the bitmap dimensions and the specified
* {@link #size}.
*/ */
public final float bitmapHeight; public final float bitmapHeight;
...@@ -195,14 +196,15 @@ public class Cue { ...@@ -195,14 +196,15 @@ public class Cue {
* fraction of the viewport height. * fraction of the viewport height.
* @param verticalPositionAnchor The vertical anchor. One of {@link #ANCHOR_TYPE_START}, * @param verticalPositionAnchor The vertical anchor. One of {@link #ANCHOR_TYPE_START},
* {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}. * {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}.
* @param width The width of the cue, expressed as a fraction of the viewport width. * @param width The width of the cue as a fraction of the viewport width.
* @param height The width of the cue, expressed as a fraction of the viewport width. * @param height The height of the cue as a fraction of the viewport height, or
* {@link #DIMEN_UNSET} if the bitmap should be displayed at its natural height for the
* specified {@code width}.
*/ */
public Cue(Bitmap bitmap, float horizontalPosition, @AnchorType int horizontalPositionAnchor, public Cue(Bitmap bitmap, float horizontalPosition, @AnchorType int horizontalPositionAnchor,
float verticalPosition, @AnchorType int verticalPositionAnchor, float width, float float verticalPosition, @AnchorType int verticalPositionAnchor, float width, float height) {
height) {
this(null, null, bitmap, verticalPosition, LINE_TYPE_FRACTION, verticalPositionAnchor, this(null, null, bitmap, verticalPosition, LINE_TYPE_FRACTION, verticalPositionAnchor,
horizontalPosition, horizontalPositionAnchor, width, height, false, Color.BLACK); horizontalPosition, horizontalPositionAnchor, width, height, false, Color.BLACK);
} }
/** /**
...@@ -248,10 +250,10 @@ public class Cue { ...@@ -248,10 +250,10 @@ public class Cue {
* @param windowColor See {@link #windowColor}. * @param windowColor See {@link #windowColor}.
*/ */
public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType, public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType,
@AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size, @AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size,
boolean windowColorSet, int windowColor) { boolean windowColorSet, int windowColor) {
this(text, textAlignment, null, line, lineType, lineAnchor, position, positionAnchor, size, this(text, textAlignment, null, line, lineType, lineAnchor, position, positionAnchor, size,
-1, windowColorSet, windowColor); DIMEN_UNSET, windowColorSet, windowColor);
} }
private Cue(CharSequence text, Alignment textAlignment, Bitmap bitmap, float line, private Cue(CharSequence text, Alignment textAlignment, Bitmap bitmap, float line,
......
...@@ -67,7 +67,7 @@ public abstract class SimpleSubtitleDecoder extends ...@@ -67,7 +67,7 @@ public abstract class SimpleSubtitleDecoder extends
SubtitleOutputBuffer outputBuffer, boolean reset) { SubtitleOutputBuffer outputBuffer, boolean reset) {
try { try {
ByteBuffer inputData = inputBuffer.data; ByteBuffer inputData = inputBuffer.data;
Subtitle subtitle = decode(inputData.array(), inputData.limit()); Subtitle subtitle = decode(inputData.array(), inputData.limit(), reset);
outputBuffer.setContent(inputBuffer.timeUs, subtitle, inputBuffer.subsampleOffsetUs); outputBuffer.setContent(inputBuffer.timeUs, subtitle, inputBuffer.subsampleOffsetUs);
// Clear BUFFER_FLAG_DECODE_ONLY (see [Internal: b/27893809]). // Clear BUFFER_FLAG_DECODE_ONLY (see [Internal: b/27893809]).
outputBuffer.clearFlag(C.BUFFER_FLAG_DECODE_ONLY); outputBuffer.clearFlag(C.BUFFER_FLAG_DECODE_ONLY);
...@@ -82,9 +82,11 @@ public abstract class SimpleSubtitleDecoder extends ...@@ -82,9 +82,11 @@ public abstract class SimpleSubtitleDecoder extends
* *
* @param data An array holding the data to be decoded, starting at position 0. * @param data An array holding the data to be decoded, starting at position 0.
* @param size The size of the data to be decoded. * @param size The size of the data to be decoded.
* @param reset Whether the decoder must be reset before decoding.
* @return The decoded {@link Subtitle}. * @return The decoded {@link Subtitle}.
* @throws SubtitleDecoderException If a decoding error occurs. * @throws SubtitleDecoderException If a decoding error occurs.
*/ */
protected abstract Subtitle decode(byte[] data, int size) throws SubtitleDecoderException; protected abstract Subtitle decode(byte[] data, int size, boolean reset)
throws SubtitleDecoderException;
} }
...@@ -24,7 +24,6 @@ import com.google.android.exoplayer2.text.tx3g.Tx3gDecoder; ...@@ -24,7 +24,6 @@ import com.google.android.exoplayer2.text.tx3g.Tx3gDecoder;
import com.google.android.exoplayer2.text.webvtt.Mp4WebvttDecoder; import com.google.android.exoplayer2.text.webvtt.Mp4WebvttDecoder;
import com.google.android.exoplayer2.text.webvtt.WebvttDecoder; import com.google.android.exoplayer2.text.webvtt.WebvttDecoder;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import java.util.List; import java.util.List;
/** /**
...@@ -86,7 +85,8 @@ public interface SubtitleDecoderFactory { ...@@ -86,7 +85,8 @@ public interface SubtitleDecoderFactory {
return clazz.asSubclass(SubtitleDecoder.class).getConstructor(Integer.TYPE) return clazz.asSubclass(SubtitleDecoder.class).getConstructor(Integer.TYPE)
.newInstance(format.accessibilityChannel); .newInstance(format.accessibilityChannel);
} else if (format.sampleMimeType.equals(MimeTypes.APPLICATION_DVBSUBS)) { } else if (format.sampleMimeType.equals(MimeTypes.APPLICATION_DVBSUBS)) {
return clazz.asSubclass(SubtitleDecoder.class).getConstructor(List.class).newInstance(format.initializationData); return clazz.asSubclass(SubtitleDecoder.class).getConstructor(List.class)
.newInstance(format.initializationData);
} else { } else {
return clazz.asSubclass(SubtitleDecoder.class).getConstructor().newInstance(); return clazz.asSubclass(SubtitleDecoder.class).getConstructor().newInstance();
} }
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
package com.google.android.exoplayer2.text.dvb; package com.google.android.exoplayer2.text.dvb;
import com.google.android.exoplayer2.text.SimpleSubtitleDecoder; import com.google.android.exoplayer2.text.SimpleSubtitleDecoder;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.util.List; import java.util.List;
/** /**
...@@ -26,27 +26,25 @@ public final class DvbDecoder extends SimpleSubtitleDecoder { ...@@ -26,27 +26,25 @@ public final class DvbDecoder extends SimpleSubtitleDecoder {
private final DvbParser parser; private final DvbParser parser;
/**
* @param initializationData The initialization data for the decoder. The initialization data
* must consist of a single byte array containing 5 bytes: flag_pes_stripped (1),
* composition_page (2), ancillary_page (2).
*/
public DvbDecoder(List<byte[]> initializationData) { public DvbDecoder(List<byte[]> initializationData) {
super("DvbDecoder"); super("DvbDecoder");
ParsableByteArray data = new ParsableByteArray(initializationData.get(0));
int subtitleCompositionPage = 1; int subtitleCompositionPage = data.readUnsignedShort();
int subtitleAncillaryPage = 1; int subtitleAncillaryPage = data.readUnsignedShort();
int flags = 0; parser = new DvbParser(subtitleCompositionPage, subtitleAncillaryPage);
byte[] tempByteArray;
if ((tempByteArray = initializationData.get(0)) != null && tempByteArray.length == 5) {
if (tempByteArray[0] == 0x01) {
flags |= DvbParser.FLAG_PES_STRIPPED_DVBSUB;
}
subtitleCompositionPage = ((tempByteArray[1] & 0xFF) << 8) | (tempByteArray[2] & 0xFF);
subtitleAncillaryPage = ((tempByteArray[3] & 0xFF) << 8) | (tempByteArray[4] & 0xFF);
}
parser = new DvbParser(subtitleCompositionPage, subtitleAncillaryPage, flags);
} }
@Override @Override
protected DvbSubtitle decode(byte[] data, int length) { protected DvbSubtitle decode(byte[] data, int length, boolean reset) {
return new DvbSubtitle(parser.dvbSubsDecode(data, length)); if (reset) {
parser.reset();
}
return new DvbSubtitle(parser.decode(data, length));
} }
}
\ No newline at end of file }
...@@ -18,41 +18,37 @@ package com.google.android.exoplayer2.text.dvb; ...@@ -18,41 +18,37 @@ package com.google.android.exoplayer2.text.dvb;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.Subtitle; import com.google.android.exoplayer2.text.Subtitle;
import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
* A representation of a DVB subtitle. * A representation of a DVB subtitle.
*/ */
/* package */ final class DvbSubtitle implements Subtitle { /* package */ final class DvbSubtitle implements Subtitle {
private final List<Cue> cues;
private final List<Cue> cues;
public DvbSubtitle(List<Cue> cues) {
if (cues == null) { public DvbSubtitle(List<Cue> cues) {
this.cues = Collections.emptyList(); this.cues = cues;
} else { }
this.cues = cues;
} @Override
} public int getNextEventTimeIndex(long timeUs) {
return C.INDEX_UNSET;
@Override }
public int getNextEventTimeIndex(long timeUs) {
return C.INDEX_UNSET; @Override
} public int getEventTimeCount() {
return 1;
@Override }
public int getEventTimeCount() {
return 1; @Override
} public long getEventTime(int index) {
return 0;
@Override }
public long getEventTime(int index) {
return 0; @Override
} public List<Cue> getCues(long timeUs) {
return cues;
@Override }
public List<Cue> getCues(long timeUs) {
return cues; }
}
}
\ No newline at end of file
...@@ -46,7 +46,7 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { ...@@ -46,7 +46,7 @@ public final class SubripDecoder extends SimpleSubtitleDecoder {
} }
@Override @Override
protected SubripSubtitle decode(byte[] bytes, int length) { protected SubripSubtitle decode(byte[] bytes, int length, boolean reset) {
ArrayList<Cue> cues = new ArrayList<>(); ArrayList<Cue> cues = new ArrayList<>();
LongArray cueTimesUs = new LongArray(); LongArray cueTimesUs = new LongArray();
ParsableByteArray subripData = new ParsableByteArray(bytes, length); ParsableByteArray subripData = new ParsableByteArray(bytes, length);
......
...@@ -94,7 +94,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { ...@@ -94,7 +94,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
} }
@Override @Override
protected TtmlSubtitle decode(byte[] bytes, int length) throws SubtitleDecoderException { protected TtmlSubtitle decode(byte[] bytes, int length, boolean reset)
throws SubtitleDecoderException {
try { try {
XmlPullParser xmlParser = xmlParserFactory.newPullParser(); XmlPullParser xmlParser = xmlParserFactory.newPullParser();
Map<String, TtmlStyle> globalStyles = new HashMap<>(); Map<String, TtmlStyle> globalStyles = new HashMap<>();
......
...@@ -35,7 +35,7 @@ public final class Tx3gDecoder extends SimpleSubtitleDecoder { ...@@ -35,7 +35,7 @@ public final class Tx3gDecoder extends SimpleSubtitleDecoder {
} }
@Override @Override
protected Subtitle decode(byte[] bytes, int length) { protected Subtitle decode(byte[] bytes, int length, boolean reset) {
parsableByteArray.reset(bytes, length); parsableByteArray.reset(bytes, length);
int textLength = parsableByteArray.readUnsignedShort(); int textLength = parsableByteArray.readUnsignedShort();
if (textLength == 0) { if (textLength == 0) {
......
...@@ -45,7 +45,8 @@ public final class Mp4WebvttDecoder extends SimpleSubtitleDecoder { ...@@ -45,7 +45,8 @@ public final class Mp4WebvttDecoder extends SimpleSubtitleDecoder {
} }
@Override @Override
protected Mp4WebvttSubtitle decode(byte[] bytes, int length) throws SubtitleDecoderException { protected Mp4WebvttSubtitle decode(byte[] bytes, int length, boolean reset)
throws SubtitleDecoderException {
// Webvtt in Mp4 samples have boxes inside of them, so we have to do a traditional box parsing: // Webvtt in Mp4 samples have boxes inside of them, so we have to do a traditional box parsing:
// first 4 bytes size and then 4 bytes type. // first 4 bytes size and then 4 bytes type.
sampleData.reset(bytes, length); sampleData.reset(bytes, length);
......
...@@ -54,7 +54,8 @@ public final class WebvttDecoder extends SimpleSubtitleDecoder { ...@@ -54,7 +54,8 @@ public final class WebvttDecoder extends SimpleSubtitleDecoder {
} }
@Override @Override
protected WebvttSubtitle decode(byte[] bytes, int length) throws SubtitleDecoderException { protected WebvttSubtitle decode(byte[] bytes, int length, boolean reset)
throws SubtitleDecoderException {
parsableWebvttData.reset(bytes, length); parsableWebvttData.reset(bytes, length);
// Initialization for consistent starting state. // Initialization for consistent starting state.
webvttCueBuilder.reset(); webvttCueBuilder.reset();
......
...@@ -90,6 +90,16 @@ public final class ParsableBitArray { ...@@ -90,6 +90,16 @@ public final class ParsableBitArray {
} }
/** /**
* Returns the current byte offset. Must only be called when the position is byte aligned.
*
* @throws IllegalStateException If the position isn't byte aligned.
*/
public int getBytePosition() {
Assertions.checkState(bitOffset == 0);
return byteOffset;
}
/**
* Sets the current bit offset. * Sets the current bit offset.
* *
* @param position The position to set. * @param position The position to set.
...@@ -177,6 +187,47 @@ public final class ParsableBitArray { ...@@ -177,6 +187,47 @@ public final class ParsableBitArray {
return returnValue; return returnValue;
} }
/**
* Aligns the position to the next byte boundary. Does nothing if the position is already aligned.
*/
public void byteAlign() {
if (bitOffset == 0) {
return;
}
bitOffset = 0;
byteOffset++;
assertValidOffset();
}
/**
* Reads the next {@code length} bytes into {@code buffer}. Must only be called when the position
* is byte aligned.
*
* @see System#arraycopy(Object, int, Object, int, int)
* @param buffer The array into which the read data should be written.
* @param offset The offset in {@code buffer} at which the read data should be written.
* @param length The number of bytes to read.
* @throws IllegalStateException If the position isn't byte aligned.
*/
public void readBytes(byte[] buffer, int offset, int length) {
Assertions.checkState(bitOffset == 0);
System.arraycopy(data, byteOffset, buffer, offset, length);
byteOffset += length;
assertValidOffset();
}
/**
* Skips the next {@code length} bytes. Must only be called when the position is byte aligned.
*
* @param length The number of bytes to read.
* @throws IllegalStateException If the position isn't byte aligned.
*/
public void skipBytes(int length) {
Assertions.checkState(bitOffset == 0);
byteOffset += length;
assertValidOffset();
}
private void assertValidOffset() { private void assertValidOffset() {
// It is fine for position to be at the end of the array, but no further. // It is fine for position to be at the end of the array, but no further.
Assertions.checkState(byteOffset >= 0 Assertions.checkState(byteOffset >= 0
......
...@@ -315,7 +315,7 @@ import com.google.android.exoplayer2.util.Util; ...@@ -315,7 +315,7 @@ import com.google.android.exoplayer2.util.Util;
float anchorX = parentLeft + (parentWidth * cuePosition); float anchorX = parentLeft + (parentWidth * cuePosition);
float anchorY = parentTop + (parentHeight * cueLine); float anchorY = parentTop + (parentHeight * cueLine);
int width = Math.round(parentWidth * cueSize); int width = Math.round(parentWidth * cueSize);
int height = cueBitmapHeight != -1 ? Math.round(parentHeight * cueBitmapHeight) int height = cueBitmapHeight != Cue.DIMEN_UNSET ? Math.round(parentHeight * cueBitmapHeight)
: Math.round(width * ((float) cueBitmap.getHeight() / cueBitmap.getWidth())); : Math.round(width * ((float) cueBitmap.getHeight() / cueBitmap.getWidth()));
int x = Math.round(cueLineAnchor == Cue.ANCHOR_TYPE_END ? (anchorX - width) int x = Math.round(cueLineAnchor == Cue.ANCHOR_TYPE_END ? (anchorX - width)
: cueLineAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorX - (width / 2)) : anchorX); : cueLineAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorX - (width / 2)) : anchorX);
......
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