diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/PtsTimestampAdjuster.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/PtsTimestampAdjuster.java new file mode 100644 index 0000000000..59d96eb003 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/PtsTimestampAdjuster.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2014 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.exoplayer.extractor.ts; + +import com.google.android.exoplayer.C; + +/** + * Scales and adjusts MPEG-2 TS presentation timestamps, taking into account an initial offset and + * timestamp rollover. + */ +public final class PtsTimestampAdjuster { + + /** + * The value one greater than the largest representable (33 bit) presentation timestamp. + */ + private static final long MAX_PTS_PLUS_ONE = 0x200000000L; + + private final long firstSampleTimestampUs; + + private long timestampOffsetUs; + private long lastPts; + + /** + * @param firstSampleTimestampUs The desired result of the first call to + * {@link #adjustTimestamp(long)}. + */ + public PtsTimestampAdjuster(long firstSampleTimestampUs) { + this.firstSampleTimestampUs = firstSampleTimestampUs; + lastPts = Long.MIN_VALUE; + } + + /** + * Resets the instance to its initial state. + */ + public void reset() { + lastPts = Long.MIN_VALUE; + } + + /** + * Scales and adjusts an MPEG-2 TS presentation timestamp. + * + * @param pts The unscaled MPEG-2 TS presentation timestamp. + * @return The adjusted timestamp in microseconds. + */ + public long adjustTimestamp(long pts) { + if (lastPts != Long.MIN_VALUE) { + // The wrap count for the current PTS may be closestWrapCount or (closestWrapCount - 1), + // and we need to snap to the one closest to lastPts. + long closestWrapCount = (lastPts + (MAX_PTS_PLUS_ONE / 2)) / MAX_PTS_PLUS_ONE; + long ptsWrapBelow = pts + (MAX_PTS_PLUS_ONE * (closestWrapCount - 1)); + long ptsWrapAbove = pts + (MAX_PTS_PLUS_ONE * closestWrapCount); + pts = Math.abs(ptsWrapBelow - lastPts) < Math.abs(ptsWrapAbove - lastPts) + ? ptsWrapBelow : ptsWrapAbove; + } + // Calculate the corresponding timestamp. + long timeUs = (pts * C.MICROS_PER_SECOND) / 90000; + // If we haven't done the initial timestamp adjustment, do it now. + if (lastPts == Long.MIN_VALUE) { + timestampOffsetUs = firstSampleTimestampUs - timeUs; + } + // Record the adjusted PTS to adjust for wraparound next time. + lastPts = pts; + return timeUs + timestampOffsetUs; + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java index b2dc044b8b..360435ba81 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer.extractor.ts; -import com.google.android.exoplayer.C; import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.extractor.ExtractorOutput; @@ -51,38 +50,33 @@ public final class TsExtractor implements Extractor { private static final int TS_STREAM_TYPE_ID3 = 0x15; private static final int TS_STREAM_TYPE_EIA608 = 0x100; // 0xFF + 1 - private static final long MAX_PTS = 0x1FFFFFFFFL; - + private final PtsTimestampAdjuster ptsTimestampAdjuster; private final ParsableByteArray tsPacketBuffer; private final ParsableBitArray tsScratch; private final boolean idrKeyframesOnly; - private final long firstSampleTimestampUs; /* package */ final SparseBooleanArray streamTypes; /* package */ final SparseArray tsPayloadReaders; // Indexed by pid // Accessed only by the loading thread. private ExtractorOutput output; - private long timestampOffsetUs; - private long lastPts; /* package */ Id3Reader id3Reader; public TsExtractor() { - this(0); + this(new PtsTimestampAdjuster(0)); } - public TsExtractor(long firstSampleTimestampUs) { - this(firstSampleTimestampUs, true); + public TsExtractor(PtsTimestampAdjuster ptsTimestampAdjuster) { + this(ptsTimestampAdjuster, true); } - public TsExtractor(long firstSampleTimestampUs, boolean idrKeyframesOnly) { - this.firstSampleTimestampUs = firstSampleTimestampUs; + public TsExtractor(PtsTimestampAdjuster ptsTimestampAdjuster, boolean idrKeyframesOnly) { this.idrKeyframesOnly = idrKeyframesOnly; tsScratch = new ParsableBitArray(new byte[3]); tsPacketBuffer = new ParsableByteArray(TS_PACKET_SIZE); streamTypes = new SparseBooleanArray(); tsPayloadReaders = new SparseArray<>(); tsPayloadReaders.put(TS_PAT_PID, new PatReader()); - lastPts = Long.MIN_VALUE; + this.ptsTimestampAdjuster = ptsTimestampAdjuster; } // Extractor implementation. @@ -108,8 +102,7 @@ public final class TsExtractor implements Extractor { @Override public void seek() { - timestampOffsetUs = 0; - lastPts = Long.MIN_VALUE; + ptsTimestampAdjuster.reset(); for (int i = 0; i < tsPayloadReaders.size(); i++) { tsPayloadReaders.valueAt(i).seek(); } @@ -160,33 +153,6 @@ public final class TsExtractor implements Extractor { // Internals. - /** - * Adjusts a PTS value to the corresponding time in microseconds, accounting for PTS wraparound. - * - * @param pts The raw PTS value. - * @return The corresponding time in microseconds. - */ - /* package */ long ptsToTimeUs(long pts) { - if (lastPts != Long.MIN_VALUE) { - // The wrap count for the current PTS may be closestWrapCount or (closestWrapCount - 1), - // and we need to snap to the one closest to lastPts. - long closestWrapCount = (lastPts + (MAX_PTS / 2)) / MAX_PTS; - long ptsWrapBelow = pts + (MAX_PTS * (closestWrapCount - 1)); - long ptsWrapAbove = pts + (MAX_PTS * closestWrapCount); - pts = Math.abs(ptsWrapBelow - lastPts) < Math.abs(ptsWrapAbove - lastPts) - ? ptsWrapBelow : ptsWrapAbove; - } - // Calculate the corresponding timestamp. - long timeUs = (pts * C.MICROS_PER_SECOND) / 90000; - // If we haven't done the initial timestamp adjustment, do it now. - if (lastPts == Long.MIN_VALUE) { - timestampOffsetUs = firstSampleTimestampUs - timeUs; - } - // Record the adjusted PTS to adjust for wraparound next time. - lastPts = pts; - return timeUs + timestampOffsetUs; - } - /** * Parses TS packet payload data. */ @@ -543,7 +509,7 @@ public final class TsExtractor implements Extractor { pesScratch.skipBits(1); // marker_bit pts |= pesScratch.readBits(15); pesScratch.skipBits(1); // marker_bit - timeUs = ptsToTimeUs(pts); + timeUs = ptsTimestampAdjuster.adjustTimestamp(pts); } } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index 6d78470caa..876d706281 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -24,6 +24,7 @@ import com.google.android.exoplayer.chunk.DataChunk; import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.ts.AdtsExtractor; +import com.google.android.exoplayer.extractor.ts.PtsTimestampAdjuster; import com.google.android.exoplayer.extractor.ts.TsExtractor; import com.google.android.exoplayer.upstream.BandwidthMeter; import com.google.android.exoplayer.upstream.DataSource; @@ -140,6 +141,7 @@ public class HlsChunkSource { private boolean live; private long durationUs; private IOException fatalError; + private PtsTimestampAdjuster ptsTimestampAdjuster; private Uri encryptionKeyUri; private byte[] encryptionKey; @@ -352,10 +354,21 @@ public class HlsChunkSource { // Configure the extractor that will read the chunk. HlsExtractorWrapper extractorWrapper; - if (previousTsChunk == null || segment.discontinuity || !format.equals(previousTsChunk.format) - || liveDiscontinuity) { - Extractor extractor = chunkUri.getLastPathSegment().endsWith(AAC_FILE_EXTENSION) - ? new AdtsExtractor(startTimeUs) : new TsExtractor(startTimeUs); + + if (previousTsChunk == null || segment.discontinuity || liveDiscontinuity + || !format.equals(previousTsChunk.format)) { + Extractor extractor; + if (chunkUri.getLastPathSegment().endsWith(AAC_FILE_EXTENSION)) { + extractor = new AdtsExtractor(startTimeUs); + } else { + if (previousTsChunk == null || segment.discontinuity || liveDiscontinuity + || ptsTimestampAdjuster == null) { + // TODO: Use this for AAC as well, along with the ID3 PRIV priv tag values with owner + // identifier com.apple.streaming.transportStreamTimestamp. + ptsTimestampAdjuster = new PtsTimestampAdjuster(startTimeUs); + } + extractor = new TsExtractor(ptsTimestampAdjuster); + } extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor, switchingVariantSpliced, adaptiveMaxWidth, adaptiveMaxHeight); } else {