| /* |
| * Copyright (C) 2016 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 static com.google.android.exoplayer2.extractor.ts.TsPayloadReader.FLAG_PAYLOAD_UNIT_START_INDICATOR; |
| |
| import android.util.SparseArray; |
| import android.util.SparseBooleanArray; |
| import android.util.SparseIntArray; |
| import androidx.annotation.IntDef; |
| import androidx.annotation.Nullable; |
| import com.google.android.exoplayer2.C; |
| import com.google.android.exoplayer2.ParserException; |
| import com.google.android.exoplayer2.extractor.Extractor; |
| import com.google.android.exoplayer2.extractor.ExtractorInput; |
| import com.google.android.exoplayer2.extractor.ExtractorOutput; |
| import com.google.android.exoplayer2.extractor.ExtractorsFactory; |
| import com.google.android.exoplayer2.extractor.PositionHolder; |
| import com.google.android.exoplayer2.extractor.SeekMap; |
| import com.google.android.exoplayer2.extractor.TrackOutput; |
| import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory.Flags; |
| import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.DvbSubtitleInfo; |
| import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; |
| import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; |
| import com.google.android.exoplayer2.util.Assertions; |
| import com.google.android.exoplayer2.util.ParsableBitArray; |
| import com.google.android.exoplayer2.util.ParsableByteArray; |
| import com.google.android.exoplayer2.util.TimestampAdjuster; |
| import com.google.android.exoplayer2.util.Util; |
| import java.io.IOException; |
| import java.lang.annotation.Documented; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * Extracts data from the MPEG-2 TS container format. |
| */ |
| public final class TsExtractor implements Extractor { |
| |
| /** Factory for {@link TsExtractor} instances. */ |
| public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new TsExtractor()}; |
| |
| /** |
| * Modes for the extractor. One of {@link #MODE_MULTI_PMT}, {@link #MODE_SINGLE_PMT} or {@link |
| * #MODE_HLS}. |
| */ |
| @Documented |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef({MODE_MULTI_PMT, MODE_SINGLE_PMT, MODE_HLS}) |
| public @interface Mode {} |
| |
| /** |
| * Behave as defined in ISO/IEC 13818-1. |
| */ |
| public static final int MODE_MULTI_PMT = 0; |
| /** |
| * Assume only one PMT will be contained in the stream, even if more are declared by the PAT. |
| */ |
| public static final int MODE_SINGLE_PMT = 1; |
| /** |
| * Enable single PMT mode, map {@link TrackOutput}s by their type (instead of PID) and ignore |
| * continuity counters. |
| */ |
| public static final int MODE_HLS = 2; |
| |
| public static final int TS_STREAM_TYPE_MPA = 0x03; |
| public static final int TS_STREAM_TYPE_MPA_LSF = 0x04; |
| public static final int TS_STREAM_TYPE_AAC_ADTS = 0x0F; |
| public static final int TS_STREAM_TYPE_AAC_LATM = 0x11; |
| public static final int TS_STREAM_TYPE_AC3 = 0x81; |
| public static final int TS_STREAM_TYPE_DTS = 0x8A; |
| public static final int TS_STREAM_TYPE_HDMV_DTS = 0x82; |
| public static final int TS_STREAM_TYPE_E_AC3 = 0x87; |
| public static final int TS_STREAM_TYPE_AC4 = 0xAC; // DVB/ATSC AC-4 Descriptor |
| public static final int TS_STREAM_TYPE_H262 = 0x02; |
| public static final int TS_STREAM_TYPE_H264 = 0x1B; |
| 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; |
| |
| // Stream types that aren't defined by the MPEG-2 TS specification. |
| public static final int TS_STREAM_TYPE_AIT = 0x101; |
| |
| public static final int TS_PACKET_SIZE = 188; |
| public static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet. |
| |
| private static final int TS_PAT_PID = 0; |
| private static final int MAX_PID_PLUS_ONE = 0x2000; |
| |
| private static final long AC3_FORMAT_IDENTIFIER = 0x41432d33; |
| private static final long E_AC3_FORMAT_IDENTIFIER = 0x45414333; |
| private static final long AC4_FORMAT_IDENTIFIER = 0x41432d34; |
| private static final long HEVC_FORMAT_IDENTIFIER = 0x48455643; |
| |
| private static final int BUFFER_SIZE = TS_PACKET_SIZE * 50; |
| private static final int SNIFF_TS_PACKET_COUNT = 5; |
| |
| private final @Mode int mode; |
| private final List<TimestampAdjuster> timestampAdjusters; |
| private final ParsableByteArray tsPacketBuffer; |
| private final SparseIntArray continuityCounters; |
| private final TsPayloadReader.Factory payloadReaderFactory; |
| private final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid |
| private final SparseBooleanArray trackIds; |
| private final SparseBooleanArray trackPids; |
| private final TsDurationReader durationReader; |
| |
| // Accessed only by the loading thread. |
| private TsBinarySearchSeeker tsBinarySearchSeeker; |
| private ExtractorOutput output; |
| private int remainingPmts; |
| private boolean tracksEnded; |
| private boolean hasOutputSeekMap; |
| private boolean pendingSeekToStart; |
| private TsPayloadReader id3Reader; |
| private int bytesSinceLastSync; |
| private int pcrPid; |
| |
| public TsExtractor() { |
| this(0); |
| } |
| |
| /** |
| * @param defaultTsPayloadReaderFlags A combination of {@link DefaultTsPayloadReaderFactory} |
| * {@code FLAG_*} values that control the behavior of the payload readers. |
| */ |
| public TsExtractor(@Flags int defaultTsPayloadReaderFlags) { |
| this(MODE_SINGLE_PMT, defaultTsPayloadReaderFlags); |
| } |
| |
| /** |
| * @param mode Mode for the extractor. One of {@link #MODE_MULTI_PMT}, {@link #MODE_SINGLE_PMT} |
| * and {@link #MODE_HLS}. |
| * @param defaultTsPayloadReaderFlags A combination of {@link DefaultTsPayloadReaderFactory} |
| * {@code FLAG_*} values that control the behavior of the payload readers. |
| */ |
| public TsExtractor(@Mode int mode, @Flags int defaultTsPayloadReaderFlags) { |
| this( |
| mode, |
| new TimestampAdjuster(0), |
| new DefaultTsPayloadReaderFactory(defaultTsPayloadReaderFlags)); |
| } |
| |
| /** |
| * @param mode Mode for the extractor. One of {@link #MODE_MULTI_PMT}, {@link #MODE_SINGLE_PMT} |
| * and {@link #MODE_HLS}. |
| * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. |
| * @param payloadReaderFactory Factory for injecting a custom set of payload readers. |
| */ |
| public TsExtractor( |
| @Mode int mode, |
| TimestampAdjuster timestampAdjuster, |
| TsPayloadReader.Factory payloadReaderFactory) { |
| this.payloadReaderFactory = Assertions.checkNotNull(payloadReaderFactory); |
| this.mode = mode; |
| if (mode == MODE_SINGLE_PMT || mode == MODE_HLS) { |
| timestampAdjusters = Collections.singletonList(timestampAdjuster); |
| } else { |
| timestampAdjusters = new ArrayList<>(); |
| timestampAdjusters.add(timestampAdjuster); |
| } |
| tsPacketBuffer = new ParsableByteArray(new byte[BUFFER_SIZE], 0); |
| trackIds = new SparseBooleanArray(); |
| trackPids = new SparseBooleanArray(); |
| tsPayloadReaders = new SparseArray<>(); |
| continuityCounters = new SparseIntArray(); |
| durationReader = new TsDurationReader(); |
| pcrPid = -1; |
| resetPayloadReaders(); |
| } |
| |
| // Extractor implementation. |
| |
| @Override |
| public boolean sniff(ExtractorInput input) throws IOException { |
| byte[] buffer = tsPacketBuffer.data; |
| input.peekFully(buffer, 0, TS_PACKET_SIZE * SNIFF_TS_PACKET_COUNT); |
| for (int startPosCandidate = 0; startPosCandidate < TS_PACKET_SIZE; startPosCandidate++) { |
| // Try to identify at least SNIFF_TS_PACKET_COUNT packets starting with TS_SYNC_BYTE. |
| boolean isSyncBytePatternCorrect = true; |
| for (int i = 0; i < SNIFF_TS_PACKET_COUNT; i++) { |
| if (buffer[startPosCandidate + i * TS_PACKET_SIZE] != TS_SYNC_BYTE) { |
| isSyncBytePatternCorrect = false; |
| break; |
| } |
| } |
| if (isSyncBytePatternCorrect) { |
| input.skipFully(startPosCandidate); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public void init(ExtractorOutput output) { |
| this.output = output; |
| } |
| |
| @Override |
| public void seek(long position, long timeUs) { |
| Assertions.checkState(mode != MODE_HLS); |
| int timestampAdjustersCount = timestampAdjusters.size(); |
| for (int i = 0; i < timestampAdjustersCount; i++) { |
| TimestampAdjuster timestampAdjuster = timestampAdjusters.get(i); |
| boolean hasNotEncounteredFirstTimestamp = |
| timestampAdjuster.getTimestampOffsetUs() == C.TIME_UNSET; |
| if (hasNotEncounteredFirstTimestamp |
| || (timestampAdjuster.getTimestampOffsetUs() != 0 |
| && timestampAdjuster.getFirstSampleTimestampUs() != timeUs)) { |
| // - If a track in the TS stream has not encountered any sample, it's going to treat the |
| // first sample encountered as timestamp 0, which is incorrect. So we have to set the first |
| // sample timestamp for that track manually. |
| // - If the timestamp adjuster has its timestamp set manually before, and now we seek to a |
| // different position, we need to set the first sample timestamp manually again. |
| timestampAdjuster.reset(); |
| timestampAdjuster.setFirstSampleTimestampUs(timeUs); |
| } |
| } |
| if (timeUs != 0 && tsBinarySearchSeeker != null) { |
| tsBinarySearchSeeker.setSeekTargetUs(timeUs); |
| } |
| tsPacketBuffer.reset(); |
| continuityCounters.clear(); |
| for (int i = 0; i < tsPayloadReaders.size(); i++) { |
| tsPayloadReaders.valueAt(i).seek(); |
| } |
| bytesSinceLastSync = 0; |
| } |
| |
| @Override |
| public void release() { |
| // Do nothing |
| } |
| |
| @Override |
| public @ReadResult int read(ExtractorInput input, PositionHolder seekPosition) |
| throws IOException { |
| long inputLength = input.getLength(); |
| if (tracksEnded) { |
| boolean canReadDuration = inputLength != C.LENGTH_UNSET && mode != MODE_HLS; |
| if (canReadDuration && !durationReader.isDurationReadFinished()) { |
| return durationReader.readDuration(input, seekPosition, pcrPid); |
| } |
| maybeOutputSeekMap(inputLength); |
| |
| if (pendingSeekToStart) { |
| pendingSeekToStart = false; |
| seek(/* position= */ 0, /* timeUs= */ 0); |
| if (input.getPosition() != 0) { |
| seekPosition.position = 0; |
| return RESULT_SEEK; |
| } |
| } |
| |
| if (tsBinarySearchSeeker != null && tsBinarySearchSeeker.isSeeking()) { |
| return tsBinarySearchSeeker.handlePendingSeek(input, seekPosition); |
| } |
| } |
| |
| if (!fillBufferWithAtLeastOnePacket(input)) { |
| return RESULT_END_OF_INPUT; |
| } |
| |
| int endOfPacket = findEndOfFirstTsPacketInBuffer(); |
| int limit = tsPacketBuffer.limit(); |
| if (endOfPacket > limit) { |
| return RESULT_CONTINUE; |
| } |
| |
| @TsPayloadReader.Flags int packetHeaderFlags = 0; |
| |
| // Note: See ISO/IEC 13818-1, section 2.4.3.2 for details of the header format. |
| int tsPacketHeader = tsPacketBuffer.readInt(); |
| if ((tsPacketHeader & 0x800000) != 0) { // transport_error_indicator |
| // There are uncorrectable errors in this packet. |
| tsPacketBuffer.setPosition(endOfPacket); |
| return RESULT_CONTINUE; |
| } |
| packetHeaderFlags |= (tsPacketHeader & 0x400000) != 0 ? FLAG_PAYLOAD_UNIT_START_INDICATOR : 0; |
| // Ignoring transport_priority (tsPacketHeader & 0x200000) |
| int pid = (tsPacketHeader & 0x1FFF00) >> 8; |
| // Ignoring transport_scrambling_control (tsPacketHeader & 0xC0) |
| boolean adaptationFieldExists = (tsPacketHeader & 0x20) != 0; |
| boolean payloadExists = (tsPacketHeader & 0x10) != 0; |
| |
| TsPayloadReader payloadReader = payloadExists ? tsPayloadReaders.get(pid) : null; |
| if (payloadReader == null) { |
| tsPacketBuffer.setPosition(endOfPacket); |
| return RESULT_CONTINUE; |
| } |
| |
| // Discontinuity check. |
| if (mode != MODE_HLS) { |
| int continuityCounter = tsPacketHeader & 0xF; |
| int previousCounter = continuityCounters.get(pid, continuityCounter - 1); |
| continuityCounters.put(pid, continuityCounter); |
| if (previousCounter == continuityCounter) { |
| // Duplicate packet found. |
| tsPacketBuffer.setPosition(endOfPacket); |
| return RESULT_CONTINUE; |
| } else if (continuityCounter != ((previousCounter + 1) & 0xF)) { |
| // Discontinuity found. |
| payloadReader.seek(); |
| } |
| } |
| |
| // Skip the adaptation field. |
| if (adaptationFieldExists) { |
| int adaptationFieldLength = tsPacketBuffer.readUnsignedByte(); |
| int adaptationFieldFlags = tsPacketBuffer.readUnsignedByte(); |
| |
| packetHeaderFlags |= |
| (adaptationFieldFlags & 0x40) != 0 // random_access_indicator. |
| ? TsPayloadReader.FLAG_RANDOM_ACCESS_INDICATOR |
| : 0; |
| tsPacketBuffer.skipBytes(adaptationFieldLength - 1 /* flags */); |
| } |
| |
| // Read the payload. |
| boolean wereTracksEnded = tracksEnded; |
| if (shouldConsumePacketPayload(pid)) { |
| tsPacketBuffer.setLimit(endOfPacket); |
| payloadReader.consume(tsPacketBuffer, packetHeaderFlags); |
| tsPacketBuffer.setLimit(limit); |
| } |
| if (mode != MODE_HLS && !wereTracksEnded && tracksEnded && inputLength != C.LENGTH_UNSET) { |
| // We have read all tracks from all PMTs in this non-live stream. Now seek to the beginning |
| // and read again to make sure we output all media, including any contained in packets prior |
| // to those containing the track information. |
| pendingSeekToStart = true; |
| } |
| |
| tsPacketBuffer.setPosition(endOfPacket); |
| return RESULT_CONTINUE; |
| } |
| |
| // Internals. |
| |
| private void maybeOutputSeekMap(long inputLength) { |
| if (!hasOutputSeekMap) { |
| hasOutputSeekMap = true; |
| if (durationReader.getDurationUs() != C.TIME_UNSET) { |
| tsBinarySearchSeeker = |
| new TsBinarySearchSeeker( |
| durationReader.getPcrTimestampAdjuster(), |
| durationReader.getDurationUs(), |
| inputLength, |
| pcrPid); |
| output.seekMap(tsBinarySearchSeeker.getSeekMap()); |
| } else { |
| output.seekMap(new SeekMap.Unseekable(durationReader.getDurationUs())); |
| } |
| } |
| } |
| |
| private boolean fillBufferWithAtLeastOnePacket(ExtractorInput input) throws IOException { |
| byte[] data = tsPacketBuffer.data; |
| // Shift bytes to the start of the buffer if there isn't enough space left at the end. |
| if (BUFFER_SIZE - tsPacketBuffer.getPosition() < TS_PACKET_SIZE) { |
| int bytesLeft = tsPacketBuffer.bytesLeft(); |
| if (bytesLeft > 0) { |
| System.arraycopy(data, tsPacketBuffer.getPosition(), data, 0, bytesLeft); |
| } |
| tsPacketBuffer.reset(data, bytesLeft); |
| } |
| // Read more bytes until we have at least one packet. |
| while (tsPacketBuffer.bytesLeft() < TS_PACKET_SIZE) { |
| int limit = tsPacketBuffer.limit(); |
| int read = input.read(data, limit, BUFFER_SIZE - limit); |
| if (read == C.RESULT_END_OF_INPUT) { |
| return false; |
| } |
| tsPacketBuffer.setLimit(limit + read); |
| } |
| return true; |
| } |
| |
| /** |
| * Returns the position of the end of the first TS packet (exclusive) in the packet buffer. |
| * |
| * <p>This may be a position beyond the buffer limit if the packet has not been read fully into |
| * the buffer, or if no packet could be found within the buffer. |
| */ |
| private int findEndOfFirstTsPacketInBuffer() throws ParserException { |
| int searchStart = tsPacketBuffer.getPosition(); |
| int limit = tsPacketBuffer.limit(); |
| int syncBytePosition = TsUtil.findSyncBytePosition(tsPacketBuffer.data, searchStart, limit); |
| // Discard all bytes before the sync byte. |
| // If sync byte is not found, this means discard the whole buffer. |
| tsPacketBuffer.setPosition(syncBytePosition); |
| int endOfPacket = syncBytePosition + TS_PACKET_SIZE; |
| if (endOfPacket > limit) { |
| bytesSinceLastSync += syncBytePosition - searchStart; |
| if (mode == MODE_HLS && bytesSinceLastSync > TS_PACKET_SIZE * 2) { |
| throw new ParserException("Cannot find sync byte. Most likely not a Transport Stream."); |
| } |
| } else { |
| // We have found a packet within the buffer. |
| bytesSinceLastSync = 0; |
| } |
| return endOfPacket; |
| } |
| |
| private boolean shouldConsumePacketPayload(int packetPid) { |
| return mode == MODE_HLS |
| || tracksEnded |
| || !trackPids.get(packetPid, /* valueIfKeyNotFound= */ false); // It's a PSI packet |
| } |
| |
| private void resetPayloadReaders() { |
| trackIds.clear(); |
| tsPayloadReaders.clear(); |
| SparseArray<TsPayloadReader> initialPayloadReaders = |
| payloadReaderFactory.createInitialPayloadReaders(); |
| int initialPayloadReadersSize = initialPayloadReaders.size(); |
| for (int i = 0; i < initialPayloadReadersSize; i++) { |
| tsPayloadReaders.put(initialPayloadReaders.keyAt(i), initialPayloadReaders.valueAt(i)); |
| } |
| tsPayloadReaders.put(TS_PAT_PID, new SectionReader(new PatReader())); |
| id3Reader = null; |
| } |
| |
| /** |
| * Parses Program Association Table data. |
| */ |
| private class PatReader implements SectionPayloadReader { |
| |
| private final ParsableBitArray patScratch; |
| |
| public PatReader() { |
| patScratch = new ParsableBitArray(new byte[4]); |
| } |
| |
| @Override |
| public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, |
| TrackIdGenerator idGenerator) { |
| // Do nothing. |
| } |
| |
| @Override |
| public void consume(ParsableByteArray sectionData) { |
| int tableId = sectionData.readUnsignedByte(); |
| if (tableId != 0x00 /* program_association_section */) { |
| // See ISO/IEC 13818-1, section 2.4.4.4 for more information on table id assignment. |
| return; |
| } |
| // section_syntax_indicator(1), '0'(1), reserved(2), section_length(12), |
| // transport_stream_id (16), reserved (2), version_number (5), current_next_indicator (1), |
| // section_number (8), last_section_number (8) |
| sectionData.skipBytes(7); |
| |
| int programCount = sectionData.bytesLeft() / 4; |
| for (int i = 0; i < programCount; i++) { |
| sectionData.readBytes(patScratch, 4); |
| int programNumber = patScratch.readBits(16); |
| patScratch.skipBits(3); // reserved (3) |
| if (programNumber == 0) { |
| patScratch.skipBits(13); // network_PID (13) |
| } else { |
| int pid = patScratch.readBits(13); |
| tsPayloadReaders.put(pid, new SectionReader(new PmtReader(pid))); |
| remainingPmts++; |
| } |
| } |
| if (mode != MODE_HLS) { |
| tsPayloadReaders.remove(TS_PAT_PID); |
| } |
| } |
| |
| } |
| |
| /** |
| * Parses Program Map Table. |
| */ |
| private class PmtReader implements SectionPayloadReader { |
| |
| 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_AC3 = 0x6A; |
| private static final int TS_PMT_DESC_AIT = 0x6F; |
| 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_DVB_EXT = 0x7F; |
| private static final int TS_PMT_DESC_DVBSUBS = 0x59; |
| |
| private static final int TS_PMT_DESC_DVB_EXT_AC4 = 0x15; |
| |
| private final ParsableBitArray pmtScratch; |
| private final SparseArray<TsPayloadReader> trackIdToReaderScratch; |
| private final SparseIntArray trackIdToPidScratch; |
| private final int pid; |
| |
| public PmtReader(int pid) { |
| pmtScratch = new ParsableBitArray(new byte[5]); |
| trackIdToReaderScratch = new SparseArray<>(); |
| trackIdToPidScratch = new SparseIntArray(); |
| this.pid = pid; |
| } |
| |
| @Override |
| public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, |
| TrackIdGenerator idGenerator) { |
| // Do nothing. |
| } |
| |
| @Override |
| public void consume(ParsableByteArray sectionData) { |
| int tableId = sectionData.readUnsignedByte(); |
| if (tableId != 0x02 /* TS_program_map_section */) { |
| // See ISO/IEC 13818-1, section 2.4.4.4 for more information on table id assignment. |
| return; |
| } |
| // TimestampAdjuster assignment. |
| TimestampAdjuster timestampAdjuster; |
| if (mode == MODE_SINGLE_PMT || mode == MODE_HLS || remainingPmts == 1) { |
| timestampAdjuster = timestampAdjusters.get(0); |
| } else { |
| timestampAdjuster = new TimestampAdjuster( |
| timestampAdjusters.get(0).getFirstSampleTimestampUs()); |
| timestampAdjusters.add(timestampAdjuster); |
| } |
| |
| // section_syntax_indicator(1), '0'(1), reserved(2), section_length(12) |
| sectionData.skipBytes(2); |
| int programNumber = sectionData.readUnsignedShort(); |
| |
| // Skip 3 bytes (24 bits), including: |
| // reserved (2), version_number (5), current_next_indicator (1), section_number (8), |
| // last_section_number (8) |
| sectionData.skipBytes(3); |
| |
| sectionData.readBytes(pmtScratch, 2); |
| // reserved (3), PCR_PID (13) |
| pmtScratch.skipBits(3); |
| pcrPid = pmtScratch.readBits(13); |
| |
| // Read program_info_length. |
| sectionData.readBytes(pmtScratch, 2); |
| pmtScratch.skipBits(4); |
| int programInfoLength = pmtScratch.readBits(12); |
| |
| // Skip the descriptors. |
| sectionData.skipBytes(programInfoLength); |
| |
| if (mode == MODE_HLS && id3Reader == null) { |
| // Setup an ID3 track regardless of whether there's a corresponding entry, in case one |
| // appears intermittently during playback. See [Internal: b/20261500]. |
| EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, null, Util.EMPTY_BYTE_ARRAY); |
| id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo); |
| id3Reader.init(timestampAdjuster, output, |
| new TrackIdGenerator(programNumber, TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE)); |
| } |
| |
| trackIdToReaderScratch.clear(); |
| trackIdToPidScratch.clear(); |
| int remainingEntriesLength = sectionData.bytesLeft(); |
| while (remainingEntriesLength > 0) { |
| sectionData.readBytes(pmtScratch, 5); |
| int streamType = pmtScratch.readBits(8); |
| pmtScratch.skipBits(3); // reserved |
| 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 || streamType == 0x05) { |
| streamType = esInfo.streamType; |
| } |
| remainingEntriesLength -= esInfoLength + 5; |
| |
| int trackId = mode == MODE_HLS ? streamType : elementaryPid; |
| if (trackIds.get(trackId)) { |
| continue; |
| } |
| |
| @Nullable |
| TsPayloadReader reader = |
| mode == MODE_HLS && streamType == TS_STREAM_TYPE_ID3 |
| ? id3Reader |
| : payloadReaderFactory.createPayloadReader(streamType, esInfo); |
| if (mode != MODE_HLS |
| || elementaryPid < trackIdToPidScratch.get(trackId, MAX_PID_PLUS_ONE)) { |
| trackIdToPidScratch.put(trackId, elementaryPid); |
| trackIdToReaderScratch.put(trackId, reader); |
| } |
| } |
| |
| int trackIdCount = trackIdToPidScratch.size(); |
| for (int i = 0; i < trackIdCount; i++) { |
| int trackId = trackIdToPidScratch.keyAt(i); |
| int trackPid = trackIdToPidScratch.valueAt(i); |
| trackIds.put(trackId, true); |
| trackPids.put(trackPid, true); |
| @Nullable TsPayloadReader reader = trackIdToReaderScratch.valueAt(i); |
| if (reader != null) { |
| if (reader != id3Reader) { |
| reader.init(timestampAdjuster, output, |
| new TrackIdGenerator(programNumber, trackId, MAX_PID_PLUS_ONE)); |
| } |
| tsPayloadReaders.put(trackPid, reader); |
| } |
| } |
| |
| if (mode == MODE_HLS) { |
| if (!tracksEnded) { |
| output.endTracks(); |
| remainingPmts = 0; |
| tracksEnded = true; |
| } |
| } else { |
| tsPayloadReaders.remove(pid); |
| remainingPmts = mode == MODE_SINGLE_PMT ? 0 : remainingPmts - 1; |
| if (remainingPmts == 0) { |
| output.endTracks(); |
| tracksEnded = true; |
| } |
| } |
| } |
| |
| /** |
| * Returns the stream info read from the available descriptors. 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 info read from the available descriptors. |
| */ |
| private EsInfo readEsInfo(ParsableByteArray data, int length) { |
| int descriptorsStartPosition = data.getPosition(); |
| int descriptorsEndPosition = descriptorsStartPosition + length; |
| int streamType = -1; |
| String language = null; |
| List<DvbSubtitleInfo> dvbSubtitleInfos = null; |
| while (data.getPosition() < descriptorsEndPosition) { |
| int descriptorTag = data.readUnsignedByte(); |
| int descriptorLength = data.readUnsignedByte(); |
| int positionOfNextDescriptor = data.getPosition() + descriptorLength; |
| if (descriptorTag == TS_PMT_DESC_REGISTRATION) { // registration_descriptor |
| long formatIdentifier = data.readUnsignedInt(); |
| if (formatIdentifier == AC3_FORMAT_IDENTIFIER) { |
| streamType = TS_STREAM_TYPE_AC3; |
| } else if (formatIdentifier == E_AC3_FORMAT_IDENTIFIER) { |
| streamType = TS_STREAM_TYPE_E_AC3; |
| } else if (formatIdentifier == AC4_FORMAT_IDENTIFIER) { |
| streamType = TS_STREAM_TYPE_AC4; |
| } else if (formatIdentifier == HEVC_FORMAT_IDENTIFIER) { |
| streamType = TS_STREAM_TYPE_H265; |
| } |
| } else if (descriptorTag == TS_PMT_DESC_AC3) { // AC-3_descriptor in DVB (ETSI EN 300 468) |
| streamType = TS_STREAM_TYPE_AC3; |
| } else if (descriptorTag == TS_PMT_DESC_EAC3) { // enhanced_AC-3_descriptor |
| streamType = TS_STREAM_TYPE_E_AC3; |
| } else if (descriptorTag == TS_PMT_DESC_DVB_EXT) { |
| // Extension descriptor in DVB (ETSI EN 300 468). |
| int descriptorTagExt = data.readUnsignedByte(); |
| if (descriptorTagExt == TS_PMT_DESC_DVB_EXT_AC4) { |
| // AC-4_descriptor in DVB (ETSI EN 300 468). |
| streamType = TS_STREAM_TYPE_AC4; |
| } |
| } else if (descriptorTag == TS_PMT_DESC_DTS) { // DTS_descriptor |
| streamType = TS_STREAM_TYPE_DTS; |
| } else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) { |
| language = data.readString(3).trim(); |
| // Audio type is ignored. |
| } else if (descriptorTag == TS_PMT_DESC_DVBSUBS) { |
| streamType = TS_STREAM_TYPE_DVBSUBS; |
| dvbSubtitleInfos = new ArrayList<>(); |
| while (data.getPosition() < positionOfNextDescriptor) { |
| String dvbLanguage = data.readString(3).trim(); |
| int dvbSubtitlingType = data.readUnsignedByte(); |
| byte[] initializationData = new byte[4]; |
| data.readBytes(initializationData, 0, 4); |
| dvbSubtitleInfos.add(new DvbSubtitleInfo(dvbLanguage, dvbSubtitlingType, |
| initializationData)); |
| } |
| } else if (descriptorTag == TS_PMT_DESC_AIT) { |
| streamType = TS_STREAM_TYPE_AIT; |
| } |
| // Skip unused bytes of current descriptor. |
| data.skipBytes(positionOfNextDescriptor - data.getPosition()); |
| } |
| data.setPosition(descriptorsEndPosition); |
| return new EsInfo(streamType, language, dvbSubtitleInfos, |
| Arrays.copyOfRange(data.data, descriptorsStartPosition, descriptorsEndPosition)); |
| } |
| |
| } |
| |
| } |