mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
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.
This commit is contained in:
parent
8c05b5b168
commit
156bc52c8f
24 changed files with 1119 additions and 1621 deletions
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
// 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 {
|
||||||
// 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 {
|
||||||
// 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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
|
|
||||||
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 {
|
||||||
|
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017 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.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses DVB subtitle data and extracts individual frames.
|
||||||
|
*/
|
||||||
|
public final class DvbSubtitleReader implements ElementaryStreamReader {
|
||||||
|
|
||||||
|
private final String language;
|
||||||
|
private final List<byte[]> initializationData;
|
||||||
|
|
||||||
|
private TrackOutput output;
|
||||||
|
private boolean writingSample;
|
||||||
|
private int bytesToCheck;
|
||||||
|
private int sampleBytesWritten;
|
||||||
|
private long sampleTimeUs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param language The subtitle language code.
|
||||||
|
* @param initializationData Initialization data to be included in the track {@link Format}.
|
||||||
|
*/
|
||||||
|
public DvbSubtitleReader(String language, byte[] initializationData) {
|
||||||
|
this.language = language;
|
||||||
|
this.initializationData = Collections.singletonList(initializationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek() {
|
||||||
|
writingSample = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
||||||
|
idGenerator.generateNewId();
|
||||||
|
this.output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
|
||||||
|
output.format(Format.createImageSampleFormat(idGenerator.getFormatId(),
|
||||||
|
MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE, initializationData, language, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
||||||
|
if (!dataAlignmentIndicator) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
writingSample = true;
|
||||||
|
sampleTimeUs = pesTimeUs;
|
||||||
|
sampleBytesWritten = 0;
|
||||||
|
bytesToCheck = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void packetFinished() {
|
||||||
|
if (writingSample) {
|
||||||
|
output.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytesWritten, 0, null);
|
||||||
|
writingSample = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void consume(ParsableByteArray data) {
|
||||||
|
if (writingSample) {
|
||||||
|
if (bytesToCheck == 2 && !checkNextByte(data, 0x20)) {
|
||||||
|
// Failed to check data_identifier
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (bytesToCheck == 1 && !checkNextByte(data, 0x00)) {
|
||||||
|
// Check and discard the subtitle_stream_id
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int bytesAvailable = data.bytesLeft();
|
||||||
|
output.sampleData(data, bytesAvailable);
|
||||||
|
sampleBytesWritten += bytesAvailable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkNextByte(ParsableByteArray data, int expectedValue) {
|
||||||
|
if (data.bytesLeft() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (data.readUnsignedByte() != expectedValue) {
|
||||||
|
writingSample = false;
|
||||||
|
}
|
||||||
|
bytesToCheck--;
|
||||||
|
return writingSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2017 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.exoplayer2.extractor.ts;
|
|
||||||
|
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
|
||||||
import com.google.android.exoplayer2.Format;
|
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
|
||||||
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Output PES packets to a {@link TrackOutput}.
|
|
||||||
*/
|
|
||||||
public final class DvbSubtitlesReader implements ElementaryStreamReader {
|
|
||||||
|
|
||||||
private class SubtitleTrack {
|
|
||||||
private String language;
|
|
||||||
private List<byte[]> initializationData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SubtitleTrack> subtitles = new ArrayList<>();
|
|
||||||
|
|
||||||
private long sampleTimeUs;
|
|
||||||
private int sampleBytesWritten;
|
|
||||||
private boolean writingSample;
|
|
||||||
|
|
||||||
private List<TrackOutput> outputTracks = new ArrayList<>();
|
|
||||||
|
|
||||||
public DvbSubtitlesReader(TsPayloadReader.EsInfo esInfo) {
|
|
||||||
int pos = 2;
|
|
||||||
|
|
||||||
while (pos < esInfo.descriptorBytes.length) {
|
|
||||||
SubtitleTrack subtitle = new SubtitleTrack();
|
|
||||||
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
|
|
||||||
public void seek() {
|
|
||||||
writingSample = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
|
||||||
TrackOutput output;
|
|
||||||
SubtitleTrack subtitle;
|
|
||||||
|
|
||||||
for (int i = 0; i < subtitles.size(); i++) {
|
|
||||||
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
|
|
||||||
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
|
||||||
if (!dataAlignmentIndicator) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
writingSample = true;
|
|
||||||
sampleTimeUs = pesTimeUs;
|
|
||||||
sampleBytesWritten = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void packetFinished() {
|
|
||||||
TrackOutput output;
|
|
||||||
|
|
||||||
for (int i = 0; i < outputTracks.size(); i++) {
|
|
||||||
output = outputTracks.get(i);
|
|
||||||
output.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytesWritten, 0, null);
|
|
||||||
}
|
|
||||||
writingSample = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void consume(ParsableByteArray data) {
|
|
||||||
if (writingSample) {
|
|
||||||
int bytesAvailable = data.bytesLeft();
|
|
||||||
TrackOutput output;
|
|
||||||
int dataPosition = data.getPosition();
|
|
||||||
|
|
||||||
for (int i = 0; i < outputTracks.size(); i++) {
|
|
||||||
data.setPosition(dataPosition);
|
|
||||||
output = outputTracks.get(i);
|
|
||||||
output.sampleData(data, bytesAvailable);
|
|
||||||
}
|
|
||||||
|
|
||||||
sampleBytesWritten += bytesAvailable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
} 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 {
|
||||||
|
|
||||||
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 {
|
||||||
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 {
|
||||||
* 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 {
|
||||||
* @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
|
||||||
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
|
||||||
*
|
*
|
||||||
* @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;
|
||||||
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 {
|
||||||
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 @@
|
||||||
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 {
|
||||||
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
|
||||||
|
|
||||||
public DvbSubtitle(List<Cue> cues) {
|
private final List<Cue> cues;
|
||||||
if (cues == null) {
|
|
||||||
this.cues = Collections.emptyList();
|
|
||||||
} else {
|
|
||||||
this.cues = cues;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public DvbSubtitle(List<Cue> cues) {
|
||||||
public int getNextEventTimeIndex(long timeUs) {
|
this.cues = cues;
|
||||||
return C.INDEX_UNSET;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getEventTimeCount() {
|
public int getNextEventTimeIndex(long timeUs) {
|
||||||
return 1;
|
return C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getEventTime(int index) {
|
public int getEventTimeCount() {
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Cue> getCues(long timeUs) {
|
public long getEventTime(int index) {
|
||||||
return cues;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public List<Cue> getCues(long timeUs) {
|
||||||
|
return cues;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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();
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,16 @@ public final class ParsableBitArray {
|
||||||
return byteOffset * 8 + bitOffset;
|
return byteOffset * 8 + bitOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
*
|
*
|
||||||
|
|
@ -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;
|
||||||
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);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue