From e2722dee39b20747b537de542135c3fb22e65e97 Mon Sep 17 00:00:00 2001 From: Sergio Moreno Mozota Date: Wed, 7 Sep 2016 07:43:23 +0200 Subject: [PATCH] TsExtractor support for language code in the audio tracks. --- .../exoplayer2/extractor/ts/Ac3Reader.java | 17 +++- .../exoplayer2/extractor/ts/AdtsReader.java | 14 +++- .../exoplayer2/extractor/ts/DtsReader.java | 15 +++- .../extractor/ts/MpegAudioReader.java | 9 ++- .../exoplayer2/extractor/ts/TsExtractor.java | 79 +++++++++++++------ 5 files changed, 103 insertions(+), 31 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java index 569f4e16df..b894793baa 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java @@ -33,6 +33,8 @@ import com.google.android.exoplayer2.util.ParsableByteArray; private static final int HEADER_SIZE = 8; + private String language; + private final ParsableBitArray headerScratchBits; private final ParsableByteArray headerScratchBytes; @@ -57,10 +59,21 @@ import com.google.android.exoplayer2.util.ParsableByteArray; * @param output Track output for extracted samples. */ public Ac3Reader(TrackOutput output) { + this(output, null); + } + + /** + * Constructs a new reader for (E-)AC-3 elementary streams. + * + * @param output Track output for extracted samples. + * @param language Track language. + */ + public Ac3Reader(TrackOutput output, String language) { super(output); headerScratchBits = new ParsableBitArray(new byte[HEADER_SIZE]); headerScratchBytes = new ParsableByteArray(headerScratchBits.data); state = STATE_FINDING_SYNC; + this.language = language; } @Override @@ -163,8 +176,8 @@ import com.google.android.exoplayer2.util.ParsableByteArray; headerScratchBits.skipBits(40); isEac3 = headerScratchBits.readBits(5) == 16; headerScratchBits.setPosition(headerScratchBits.getPosition() - 45); - format = isEac3 ? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, null, null, null) - : Ac3Util.parseAc3SyncframeFormat(headerScratchBits, null, null, null); + format = isEac3 ? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, null, language, null) + : Ac3Util.parseAc3SyncframeFormat(headerScratchBits, null, language, null); output.format(format); } sampleSize = isEac3 ? Ac3Util.parseEAc3SyncframeSize(headerScratchBits.data) diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java index f6579eeb41..9f688e556f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java @@ -42,6 +42,8 @@ import java.util.Collections; private static final int HEADER_SIZE = 5; private static final int CRC_SIZE = 2; + private String language; + // Match states used while looking for the next sample private static final int MATCH_STATE_VALUE_SHIFT = 8; private static final int MATCH_STATE_START = 1 << MATCH_STATE_VALUE_SHIFT; @@ -80,6 +82,15 @@ import java.util.Collections; * @param id3Output A {@link TrackOutput} to which ID3 samples should be written. */ public AdtsReader(TrackOutput output, TrackOutput id3Output) { + this(output, id3Output, null); + } + + /** + * @param output A {@link TrackOutput} to which AAC samples should be written. + * @param id3Output A {@link TrackOutput} to which ID3 samples should be written. + * @param language Track language. + */ + public AdtsReader(TrackOutput output, TrackOutput id3Output, String language) { super(output); this.id3Output = id3Output; id3Output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null, @@ -87,6 +98,7 @@ import java.util.Collections; adtsScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]); id3HeaderBuffer = new ParsableByteArray(Arrays.copyOf(ID3_IDENTIFIER, ID3_HEADER_SIZE)); setFindingSampleState(); + this.language = language; } @Override @@ -278,7 +290,7 @@ import java.util.Collections; Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first, - Collections.singletonList(audioSpecificConfig), null, 0, null); + Collections.singletonList(audioSpecificConfig), null, 0, language); // In this class a sample is an access unit, but the MediaFormat sample rate specifies the // number of PCM audio samples per second. sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / format.sampleRate; diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java index 35b73691eb..d8356a427c 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java @@ -34,6 +34,8 @@ import com.google.android.exoplayer2.util.ParsableByteArray; private static final int SYNC_VALUE = 0x7FFE8001; private static final int SYNC_VALUE_SIZE = 4; + private String language; + private final ParsableByteArray headerScratchBytes; private int state; @@ -56,6 +58,16 @@ import com.google.android.exoplayer2.util.ParsableByteArray; * @param output Track output for extracted samples. */ public DtsReader(TrackOutput output) { + this(output, null); + } + + /** + * Constructs a new reader for DTS elementary streams. + * + * @param output Track output for extracted samples. + * @param language Track language. + */ + public DtsReader(TrackOutput output, String language) { super(output); headerScratchBytes = new ParsableByteArray(new byte[HEADER_SIZE]); headerScratchBytes.data[0] = (byte) ((SYNC_VALUE >> 24) & 0xFF); @@ -63,6 +75,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; headerScratchBytes.data[2] = (byte) ((SYNC_VALUE >> 8) & 0xFF); headerScratchBytes.data[3] = (byte) (SYNC_VALUE & 0xFF); state = STATE_FINDING_SYNC; + this.language = language; } @Override @@ -155,7 +168,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; private void parseHeader() { byte[] frameData = headerScratchBytes.data; if (format == null) { - format = DtsUtil.parseDtsFormat(frameData, null, null, null); + format = DtsUtil.parseDtsFormat(frameData, null, language, null); output.format(format); } sampleSize = DtsUtil.getDtsFrameSize(frameData); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java index 381a52b4bc..20f55b4301 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java @@ -32,6 +32,8 @@ import com.google.android.exoplayer2.util.ParsableByteArray; private static final int HEADER_SIZE = 4; + private String language; + private final ParsableByteArray headerScratch; private final MpegAudioHeader header; @@ -50,12 +52,17 @@ import com.google.android.exoplayer2.util.ParsableByteArray; private long timeUs; public MpegAudioReader(TrackOutput output) { + this(output, null); + } + + public MpegAudioReader(TrackOutput output, String language) { super(output); state = STATE_FINDING_HEADER; // The first byte of an MPEG Audio frame header is always 0xFF. headerScratch = new ParsableByteArray(4); headerScratch.data[0] = (byte) 0xFF; header = new MpegAudioHeader(); + this.language = language; } @Override @@ -164,7 +171,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate; Format format = Format.createAudioSampleFormat(null, header.mimeType, null, Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels, header.sampleRate, null, null, 0, - null); + language); output.format(format); hasOutputFormat = true; } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index 5528a63b7a..b0a6e23fe4 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -334,6 +334,32 @@ public final class TsExtractor implements Extractor { private int sectionBytesRead; private int crc; + private static final int TS_PMT_DESC_REGISTRATION = 0x05; + private static final int TS_PMT_DESC_ISO639_LANG = 0x0A; + private static final int TS_PMT_DESC_VBI_DATA = 0x45; + private static final int TS_PMT_DESC_VBI_TELETEXT = 0x46; + private static final int TS_PMT_DESC_TELETEXT = 0x56; + private static final int TS_PMT_DESC_SUBTITLING = 0x59; + 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_AAC = 0x7C; + + class EsInfo { + int streamType; + String streamLanguage; + int audioType; + + public EsInfo() { + // REGISTRATION + streamType = -1; + + // ISO639LANG + streamLanguage = null; + audioType = -1; + } + } + public PmtReader() { pmtScratch = new ParsableBitArray(new byte[5]); sectionData = new ParsableByteArray(); @@ -404,11 +430,9 @@ public final class TsExtractor implements Extractor { int elementaryPid = pmtScratch.readBits(13); pmtScratch.skipBits(4); // reserved int esInfoLength = pmtScratch.readBits(12); // ES_info_length + EsInfo esInfo = readEsInfo(sectionData, esInfoLength); if (streamType == 0x06) { - // Read descriptors in PES packets containing private data. - streamType = readPrivateDataStreamType(sectionData, esInfoLength); - } else { - sectionData.skipBytes(esInfoLength); + streamType = esInfo.streamType; } remainingEntriesLength -= esInfoLength + 5; int trackId = (workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 ? streamType : elementaryPid; @@ -418,22 +442,22 @@ public final class TsExtractor implements Extractor { ElementaryStreamReader pesPayloadReader; switch (streamType) { case TS_STREAM_TYPE_MPA: - pesPayloadReader = new MpegAudioReader(output.track(trackId)); + pesPayloadReader = new MpegAudioReader(output.track(trackId), esInfo.streamLanguage); break; case TS_STREAM_TYPE_MPA_LSF: - pesPayloadReader = new MpegAudioReader(output.track(trackId)); + pesPayloadReader = new MpegAudioReader(output.track(trackId), esInfo.streamLanguage); break; case TS_STREAM_TYPE_AAC: pesPayloadReader = (workaroundFlags & WORKAROUND_IGNORE_AAC_STREAM) != 0 ? null - : new AdtsReader(output.track(trackId), new DummyTrackOutput()); + : new AdtsReader(output.track(trackId), new DummyTrackOutput(), esInfo.streamLanguage); break; case TS_STREAM_TYPE_AC3: case TS_STREAM_TYPE_E_AC3: - pesPayloadReader = new Ac3Reader(output.track(trackId)); + pesPayloadReader = new Ac3Reader(output.track(trackId), esInfo.streamLanguage); break; case TS_STREAM_TYPE_DTS: case TS_STREAM_TYPE_HDMV_DTS: - pesPayloadReader = new DtsReader(output.track(trackId)); + pesPayloadReader = new DtsReader(output.track(trackId), esInfo.streamLanguage); break; case TS_STREAM_TYPE_H262: pesPayloadReader = new H262Reader(output.track(trackId)); @@ -472,42 +496,45 @@ public final class TsExtractor implements Extractor { } /** - * Returns the stream type read from a registration descriptor in private data, or -1 if no - * stream type is present. Sets {@code data}'s position to the end of the descriptors. + * Returns the stream info read from the available descriptors, or -1 if no + * descriptors are present. Sets {@code data}'s position to the end of the descriptors. * * @param data A buffer with its position set to the start of the first descriptor. * @param length The length of descriptors to read from the current position in {@code data}. - * @return The stream type read from a registration descriptor in private data, or -1 if no - * stream type is present. + * @return The stream info read from the available descriptors, or -1 if no + * descriptors are present. */ - private int readPrivateDataStreamType(ParsableByteArray data, int length) { - int streamType = -1; + private EsInfo readEsInfo(ParsableByteArray data, int length) { + EsInfo esInfo = new EsInfo(); int descriptorsEndPosition = data.getPosition() + length; while (data.getPosition() < descriptorsEndPosition) { int descriptorTag = data.readUnsignedByte(); int descriptorLength = data.readUnsignedByte(); - if (descriptorTag == 0x05) { // registration_descriptor + if (descriptorTag == TS_PMT_DESC_REGISTRATION) { // registration_descriptor long formatIdentifier = data.readUnsignedInt(); if (formatIdentifier == AC3_FORMAT_IDENTIFIER) { - streamType = TS_STREAM_TYPE_AC3; + esInfo.streamType = TS_STREAM_TYPE_AC3; } else if (formatIdentifier == E_AC3_FORMAT_IDENTIFIER) { - streamType = TS_STREAM_TYPE_E_AC3; + esInfo.streamType = TS_STREAM_TYPE_E_AC3; } else if (formatIdentifier == HEVC_FORMAT_IDENTIFIER) { - streamType = TS_STREAM_TYPE_H265; + esInfo.streamType = TS_STREAM_TYPE_H265; } break; - } else if (descriptorTag == 0x6A) { // AC-3_descriptor in DVB (ETSI EN 300 468) - streamType = TS_STREAM_TYPE_AC3; - } else if (descriptorTag == 0x7A) { // enhanced_AC-3_descriptor - streamType = TS_STREAM_TYPE_E_AC3; - } else if (descriptorTag == 0x7B) { // DTS_descriptor - streamType = TS_STREAM_TYPE_DTS; + } else if (descriptorTag == TS_PMT_DESC_AC3) { // AC-3_descriptor in DVB (ETSI EN 300 468) + esInfo.streamType = TS_STREAM_TYPE_AC3; + } else if (descriptorTag == TS_PMT_DESC_EAC3) { // enhanced_AC-3_descriptor + esInfo.streamType = TS_STREAM_TYPE_E_AC3; + } else if (descriptorTag == TS_PMT_DESC_DTS) { // DTS_descriptor + esInfo.streamType = TS_STREAM_TYPE_DTS; + } else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) { + esInfo.streamLanguage = new String(data.data, data.getPosition(), 3).trim(); + esInfo.audioType = data.data[data.getPosition() + 3]; } data.skipBytes(descriptorLength); } data.setPosition(descriptorsEndPosition); - return streamType; + return esInfo; } }