Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DVB subtitles support #1781

Merged
merged 7 commits into from
Apr 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions library/core/proguard-rules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@
-keepclassmembers class com.google.android.exoplayer2.text.cea.Cea708Decoder {
public <init>(int);
}
-keepclassmembers class com.google.android.exoplayer2.text.dvb.DvbDecoder {
public <init>(java.util.List);
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ public Extractor[] createExtractors() {
private static final String CODEC_ID_SUBRIP = "S_TEXT/UTF8";
private static final String CODEC_ID_VOBSUB = "S_VOBSUB";
private static final String CODEC_ID_PGS = "S_HDMV/PGS";
private static final String CODEC_ID_DVBSUB = "S_DVBSUB";

private static final int VORBIS_MAX_INPUT_SIZE = 8192;
private static final int OPUS_MAX_INPUT_SIZE = 5760;
Expand Down Expand Up @@ -1233,7 +1234,8 @@ private static boolean isCodecSupported(String codecId) {
|| CODEC_ID_PCM_INT_LIT.equals(codecId)
|| CODEC_ID_SUBRIP.equals(codecId)
|| CODEC_ID_VOBSUB.equals(codecId)
|| CODEC_ID_PGS.equals(codecId);
|| CODEC_ID_PGS.equals(codecId)
|| CODEC_ID_DVBSUB.equals(codecId);
}

/**
Expand Down Expand Up @@ -1461,6 +1463,11 @@ public void initializeOutput(ExtractorOutput output, int trackId) throws ParserE
case CODEC_ID_PGS:
mimeType = MimeTypes.APPLICATION_PGS;
break;
case CODEC_ID_DVBSUB:
mimeType = MimeTypes.APPLICATION_DVBSUBS;
initializationData = Collections.singletonList(new byte[] {
(byte) 0x01, codecPrivate[0], codecPrivate[1], codecPrivate[2], codecPrivate[3]});
break;
default:
throw new ParserException("Unrecognized codec identifier.");
}
Expand Down Expand Up @@ -1495,7 +1502,8 @@ public void initializeOutput(ExtractorOutput output, int trackId) throws ParserE
format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, selectionFlags, language, drmInitData);
} else if (MimeTypes.APPLICATION_VOBSUB.equals(mimeType)
|| MimeTypes.APPLICATION_PGS.equals(mimeType)) {
|| MimeTypes.APPLICATION_PGS.equals(mimeType)
|| MimeTypes.APPLICATION_DVBSUBS.equals(mimeType)) {
type = C.TRACK_TYPE_TEXT;
format = Format.createImageSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, initializationData, language, drmInitData);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) {
? null : new SectionReader(new SpliceInfoSectionReader());
case TsExtractor.TS_STREAM_TYPE_ID3:
return new PesReader(new Id3Reader());
case TsExtractor.TS_STREAM_TYPE_DVBSUBS:
return new PesReader(new DvbSubtitlesReader(esInfo));
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public Extractor[] createExtractors() {
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_SPLICE_INFO = 0x86;
public static final int TS_STREAM_TYPE_DVBSUBS = 0x59;

private static final int TS_PACKET_SIZE = 188;
private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet.
Expand Down Expand Up @@ -356,6 +357,7 @@ private class PmtReader implements SectionPayloadReader {
private static final int TS_PMT_DESC_AC3 = 0x6A;
private static final int TS_PMT_DESC_EAC3 = 0x7A;
private static final int TS_PMT_DESC_DTS = 0x7B;
private static final int TS_PMT_DESC_DVBSUBS = 0x59;

private final ParsableBitArray pmtScratch;
private final int pid;
Expand Down Expand Up @@ -498,6 +500,9 @@ private EsInfo readEsInfo(ParsableByteArray data, int length) {
} else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) {
language = new String(data.data, data.getPosition(), 3).trim();
// Audio type is ignored.
} else if (descriptorTag == TS_PMT_DESC_DVBSUBS) {
streamType = TS_STREAM_TYPE_DVBSUBS;
language = new String(data.data, data.getPosition(), 3).trim();
}
// Skip unused bytes of current descriptor.
data.skipBytes(positionOfNextDescriptor - data.getPosition());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ public class Cue {
*/
public final float size;

/**
* The bitmap height as a fraction of the of the viewport size, or -1 if the bitmap should be
* displayed at its natural height given for its specified {@link #size}.
*/
public final float bitmapHeight;

/**
* Specifies whether or not the {@link #windowColor} property is set.
*/
Expand All @@ -190,11 +196,13 @@ public class Cue {
* @param verticalPositionAnchor The vertical anchor. One of {@link #ANCHOR_TYPE_START},
* {@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 height The width of the cue, expressed as a fraction of the viewport width.
*/
public Cue(Bitmap bitmap, float horizontalPosition, @AnchorType int horizontalPositionAnchor,
float verticalPosition, @AnchorType int verticalPositionAnchor, float width) {
float verticalPosition, @AnchorType int verticalPositionAnchor, float width, float
height) {
this(null, null, bitmap, verticalPosition, LINE_TYPE_FRACTION, verticalPositionAnchor,
horizontalPosition, horizontalPositionAnchor, width, false, Color.BLACK);
horizontalPosition, horizontalPositionAnchor, width, height, false, Color.BLACK);
}

/**
Expand Down Expand Up @@ -240,15 +248,16 @@ public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int
* @param windowColor See {@link #windowColor}.
*/
public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType,
@AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size,
boolean windowColorSet, int windowColor) {
@AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size,
boolean windowColorSet, int windowColor) {
this(text, textAlignment, null, line, lineType, lineAnchor, position, positionAnchor, size,
windowColorSet, windowColor);
-1, windowColorSet, windowColor);
}

private Cue(CharSequence text, Alignment textAlignment, Bitmap bitmap, float line,
@LineType int lineType, @AnchorType int lineAnchor, float position,
@AnchorType int positionAnchor, float size, boolean windowColorSet, int windowColor) {
@AnchorType int positionAnchor, float size, float bitmapHeight, boolean windowColorSet,
int windowColor) {
this.text = text;
this.textAlignment = textAlignment;
this.bitmap = bitmap;
Expand All @@ -258,6 +267,7 @@ private Cue(CharSequence text, Alignment textAlignment, Bitmap bitmap, float lin
this.position = position;
this.positionAnchor = positionAnchor;
this.size = size;
this.bitmapHeight = bitmapHeight;
this.windowColorSet = windowColorSet;
this.windowColor = windowColor;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import com.google.android.exoplayer2.text.webvtt.WebvttDecoder;
import com.google.android.exoplayer2.util.MimeTypes;

import java.util.List;

/**
* A factory for {@link SubtitleDecoder} instances.
*/
Expand Down Expand Up @@ -83,6 +85,8 @@ public SubtitleDecoder createDecoder(Format format) {
} else if (format.sampleMimeType.equals(MimeTypes.APPLICATION_CEA708)) {
return clazz.asSubclass(SubtitleDecoder.class).getConstructor(Integer.TYPE)
.newInstance(format.accessibilityChannel);
} else if (format.sampleMimeType.equals(MimeTypes.APPLICATION_DVBSUBS)) {
return clazz.asSubclass(SubtitleDecoder.class).getConstructor(List.class).newInstance(format.initializationData);
} else {
return clazz.asSubclass(SubtitleDecoder.class).getConstructor().newInstance();
}
Expand Down Expand Up @@ -112,6 +116,8 @@ private Class<?> getDecoderClass(String mimeType) {
return Class.forName("com.google.android.exoplayer2.text.cea.Cea608Decoder");
case MimeTypes.APPLICATION_CEA708:
return Class.forName("com.google.android.exoplayer2.text.cea.Cea708Decoder");
case MimeTypes.APPLICATION_DVBSUBS:
return Class.forName("com.google.android.exoplayer2.text.dvb.DvbDecoder");
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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.text.dvb;

import com.google.android.exoplayer2.text.SimpleSubtitleDecoder;

import java.util.List;

/**
* A {@link SimpleSubtitleDecoder} for DVB Subtitles.
*/
public final class DvbDecoder extends SimpleSubtitleDecoder {

private final DvbParser parser;

public DvbDecoder(List<byte[]> initializationData) {
super("DvbDecoder");

int subtitleCompositionPage = 1;
int subtitleAncillaryPage = 1;
int flags = 0;
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
protected DvbSubtitle decode(byte[] data, int length) {
return new DvbSubtitle(parser.dvbSubsDecode(data, length));
}
}
Loading