mirror of
https://github.com/androidx/media.git
synced 2025-05-17 12:39:52 +08:00
Add support for multiple subtitle tracks per PID and subtitle type identification
This commit is contained in:
parent
da1b55ed4c
commit
3b7f47551c
@ -156,7 +156,7 @@ public final class TsExtractorTest extends InstrumentationTestCase {
|
|||||||
@Override
|
@Override
|
||||||
public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) {
|
public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) {
|
||||||
if (provideCustomEsReader && streamType == 3) {
|
if (provideCustomEsReader && streamType == 3) {
|
||||||
esReader = new CustomEsReader(esInfo.language);
|
esReader = new CustomEsReader(esInfo.languagesInfo.get(0).languageCode);
|
||||||
return new PesReader(esReader);
|
return new PesReader(esReader);
|
||||||
} else {
|
} else {
|
||||||
return defaultFactory.createPayloadReader(streamType, esInfo);
|
return defaultFactory.createPayloadReader(streamType, esInfo);
|
||||||
|
@ -93,16 +93,16 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
|
|||||||
switch (streamType) {
|
switch (streamType) {
|
||||||
case TsExtractor.TS_STREAM_TYPE_MPA:
|
case TsExtractor.TS_STREAM_TYPE_MPA:
|
||||||
case TsExtractor.TS_STREAM_TYPE_MPA_LSF:
|
case TsExtractor.TS_STREAM_TYPE_MPA_LSF:
|
||||||
return new PesReader(new MpegAudioReader(esInfo.language));
|
return new PesReader(new MpegAudioReader(esInfo.languagesInfo.get(0).languageCode));
|
||||||
case TsExtractor.TS_STREAM_TYPE_AAC:
|
case TsExtractor.TS_STREAM_TYPE_AAC:
|
||||||
return isSet(FLAG_IGNORE_AAC_STREAM)
|
return isSet(FLAG_IGNORE_AAC_STREAM)
|
||||||
? null : new PesReader(new AdtsReader(false, esInfo.language));
|
? null : new PesReader(new AdtsReader(false, esInfo.languagesInfo.get(0).languageCode));
|
||||||
case TsExtractor.TS_STREAM_TYPE_AC3:
|
case TsExtractor.TS_STREAM_TYPE_AC3:
|
||||||
case TsExtractor.TS_STREAM_TYPE_E_AC3:
|
case TsExtractor.TS_STREAM_TYPE_E_AC3:
|
||||||
return new PesReader(new Ac3Reader(esInfo.language));
|
return new PesReader(new Ac3Reader(esInfo.languagesInfo.get(0).languageCode));
|
||||||
case TsExtractor.TS_STREAM_TYPE_DTS:
|
case TsExtractor.TS_STREAM_TYPE_DTS:
|
||||||
case TsExtractor.TS_STREAM_TYPE_HDMV_DTS:
|
case TsExtractor.TS_STREAM_TYPE_HDMV_DTS:
|
||||||
return new PesReader(new DtsReader(esInfo.language));
|
return new PesReader(new DtsReader(esInfo.languagesInfo.get(0).languageCode));
|
||||||
case TsExtractor.TS_STREAM_TYPE_H262:
|
case TsExtractor.TS_STREAM_TYPE_H262:
|
||||||
return new PesReader(new H262Reader());
|
return new PesReader(new H262Reader());
|
||||||
case TsExtractor.TS_STREAM_TYPE_H264:
|
case TsExtractor.TS_STREAM_TYPE_H264:
|
||||||
@ -118,7 +118,7 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
|
|||||||
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(
|
return new PesReader(
|
||||||
new DvbSubtitleReader(esInfo.language, esInfo.dvbSubtitleInitializationData));
|
new DvbSubtitleReader(esInfo));
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -20,9 +20,12 @@ import com.google.android.exoplayer2.Format;
|
|||||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
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.extractor.ts.TsPayloadReader.EsInfo;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.LanguageInfo;
|
||||||
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.Collections;
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,22 +33,19 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public final class DvbSubtitleReader implements ElementaryStreamReader {
|
public final class DvbSubtitleReader implements ElementaryStreamReader {
|
||||||
|
|
||||||
private final String language;
|
private final List<LanguageInfo> languages;
|
||||||
private final List<byte[]> initializationData;
|
private List<TrackOutput> outputTracks = new ArrayList<>();
|
||||||
|
|
||||||
private TrackOutput output;
|
|
||||||
private boolean writingSample;
|
private boolean writingSample;
|
||||||
private int bytesToCheck;
|
private int bytesToCheck;
|
||||||
private int sampleBytesWritten;
|
private int sampleBytesWritten;
|
||||||
private long sampleTimeUs;
|
private long sampleTimeUs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param language The subtitle language code.
|
* @param esInfo Information associated to the elementary stream.
|
||||||
* @param initializationData Initialization data to be included in the track {@link Format}.
|
|
||||||
*/
|
*/
|
||||||
public DvbSubtitleReader(String language, byte[] initializationData) {
|
public DvbSubtitleReader(EsInfo esInfo) {
|
||||||
this.language = language;
|
this.languages = esInfo.languagesInfo;
|
||||||
this.initializationData = Collections.singletonList(initializationData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -56,9 +56,24 @@ public final class DvbSubtitleReader implements ElementaryStreamReader {
|
|||||||
@Override
|
@Override
|
||||||
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
||||||
idGenerator.generateNewId();
|
idGenerator.generateNewId();
|
||||||
this.output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
|
|
||||||
|
TrackOutput output;
|
||||||
|
LanguageInfo language;
|
||||||
|
|
||||||
|
for (int i = 0; i < languages.size(); i++) {
|
||||||
|
language = languages.get(i);
|
||||||
|
idGenerator.generateNewId();
|
||||||
|
|
||||||
|
if (((language.programElementType & 0xF0 ) >> 4 ) == 2 ) {
|
||||||
|
language.languageCode += " for hard of hearing";
|
||||||
|
}
|
||||||
|
|
||||||
|
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
|
||||||
output.format(Format.createImageSampleFormat(idGenerator.getFormatId(),
|
output.format(Format.createImageSampleFormat(idGenerator.getFormatId(),
|
||||||
MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE, initializationData, language, null));
|
MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE,
|
||||||
|
language.initializationData, language.languageCode, null));
|
||||||
|
outputTracks.add(output);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -75,7 +90,12 @@ public final class DvbSubtitleReader implements ElementaryStreamReader {
|
|||||||
@Override
|
@Override
|
||||||
public void packetFinished() {
|
public void packetFinished() {
|
||||||
if (writingSample) {
|
if (writingSample) {
|
||||||
|
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);
|
output.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytesWritten, 0, null);
|
||||||
|
}
|
||||||
writingSample = false;
|
writingSample = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,7 +112,13 @@ public final class DvbSubtitleReader implements ElementaryStreamReader {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int bytesAvailable = data.bytesLeft();
|
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);
|
output.sampleData(data, bytesAvailable);
|
||||||
|
}
|
||||||
sampleBytesWritten += bytesAvailable;
|
sampleBytesWritten += bytesAvailable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import com.google.android.exoplayer2.extractor.SeekMap;
|
|||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory.Flags;
|
import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory.Flags;
|
||||||
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo;
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.LanguageInfo;
|
||||||
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.ParsableBitArray;
|
import com.google.android.exoplayer2.util.ParsableBitArray;
|
||||||
@ -418,7 +419,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, null, new byte[0]);
|
EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, 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));
|
||||||
@ -487,8 +488,7 @@ public final class TsExtractor implements Extractor {
|
|||||||
int descriptorsStartPosition = data.getPosition();
|
int descriptorsStartPosition = data.getPosition();
|
||||||
int descriptorsEndPosition = descriptorsStartPosition + length;
|
int descriptorsEndPosition = descriptorsStartPosition + length;
|
||||||
int streamType = -1;
|
int streamType = -1;
|
||||||
String language = null;
|
List<LanguageInfo> languages = 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();
|
||||||
@ -509,21 +509,35 @@ public final class TsExtractor implements Extractor {
|
|||||||
} else if (descriptorTag == TS_PMT_DESC_DTS) { // DTS_descriptor
|
} else if (descriptorTag == TS_PMT_DESC_DTS) { // DTS_descriptor
|
||||||
streamType = TS_STREAM_TYPE_DTS;
|
streamType = TS_STREAM_TYPE_DTS;
|
||||||
} else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) {
|
} else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) {
|
||||||
language = new String(data.data, data.getPosition(), 3).trim();
|
int position = data.getPosition();
|
||||||
// Audio type is ignored.
|
languages = Collections.singletonList(
|
||||||
|
new LanguageInfo(new String(new byte[]
|
||||||
|
{data.data[position++], data.data[position++], data.data[position++]}).trim(),
|
||||||
|
data.data[position], null));
|
||||||
} 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();
|
int position = data.getPosition();
|
||||||
data.skipBytes(4); // Skip language (3) + subtitling_type (1)
|
String language;
|
||||||
// Init data: composition_page (2), ancillary_page (2)
|
byte programElementType;
|
||||||
dvbSubtitleInitializationData = new byte[4];
|
byte[] buffer;
|
||||||
data.readBytes(dvbSubtitleInitializationData, 0, 4);
|
languages = new ArrayList<>();
|
||||||
|
while (position < positionOfNextDescriptor) {
|
||||||
|
buffer = new byte[4];
|
||||||
|
language = new String(new byte[]
|
||||||
|
{data.data[position++], data.data[position++], data.data[position++]}).trim();
|
||||||
|
programElementType = data.data[position++];
|
||||||
|
data.setPosition(position);
|
||||||
|
data.readBytes(buffer, 0,4);
|
||||||
|
languages.add(
|
||||||
|
new LanguageInfo(language, programElementType, Collections.singletonList(buffer)));
|
||||||
|
position += 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, dvbSubtitleInitializationData,
|
return new EsInfo(streamType, languages,
|
||||||
Arrays.copyOfRange(data.data, descriptorsStartPosition, descriptorsEndPosition));
|
Arrays.copyOfRange(data.data, descriptorsStartPosition, descriptorsEndPosition));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@ import com.google.android.exoplayer2.extractor.TrackOutput;
|
|||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import com.google.android.exoplayer2.util.TimestampAdjuster;
|
import com.google.android.exoplayer2.util.TimestampAdjuster;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses TS packet payload data.
|
* Parses TS packet payload data.
|
||||||
*/
|
*/
|
||||||
@ -59,26 +61,33 @@ public interface TsPayloadReader {
|
|||||||
final class EsInfo {
|
final class EsInfo {
|
||||||
|
|
||||||
public final int streamType;
|
public final int streamType;
|
||||||
public final String language;
|
public final List<LanguageInfo> languagesInfo;
|
||||||
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 languagesInfo Language or languages info of the associated program element
|
||||||
* @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[] dvbSubtitleInitializationData,
|
public EsInfo(int streamType, List<LanguageInfo> languagesInfo, byte[] descriptorBytes) {
|
||||||
byte[] descriptorBytes) {
|
|
||||||
this.streamType = streamType;
|
this.streamType = streamType;
|
||||||
this.language = language;
|
this.languagesInfo = languagesInfo;
|
||||||
this.dvbSubtitleInitializationData = dvbSubtitleInitializationData;
|
|
||||||
this.descriptorBytes = descriptorBytes;
|
this.descriptorBytes = descriptorBytes;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class LanguageInfo {
|
||||||
|
|
||||||
|
public String languageCode;
|
||||||
|
public final byte programElementType;
|
||||||
|
public final List<byte[]> initializationData;
|
||||||
|
|
||||||
|
LanguageInfo (String languageCode, byte programElementType, List<byte[]> initializationData) {
|
||||||
|
this.languageCode = languageCode;
|
||||||
|
this.programElementType = programElementType;
|
||||||
|
this.initializationData = initializationData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user