From c3c176d93c03fe9578161d35f4552d453a3a314e Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Fri, 25 Nov 2016 10:26:05 -0800 Subject: [PATCH] Move HLS extractor construction to HlsMediaChunk This allows ID3 PRIV timestamp extraction and Extractor Sniffing. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=140209568 --- .../exoplayer2/source/hls/HlsChunkSource.java | 85 +-------------- .../exoplayer2/source/hls/HlsMediaChunk.java | 102 ++++++++++++++---- 2 files changed, 85 insertions(+), 102 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index 80c378a666..87e0aebb1c 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -17,17 +17,9 @@ package com.google.android.exoplayer2.source.hls; import android.net.Uri; import android.os.SystemClock; -import android.text.TextUtils; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.TimestampAdjuster; -import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; -import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor; -import com.google.android.exoplayer2.extractor.ts.Ac3Extractor; -import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; -import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; -import com.google.android.exoplayer2.extractor.ts.TsExtractor; import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.chunk.Chunk; @@ -41,7 +33,6 @@ import com.google.android.exoplayer2.trackselection.BaseTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; -import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.UriUtil; import com.google.android.exoplayer2.util.Util; import java.io.IOException; @@ -89,14 +80,6 @@ import java.util.Locale; } - private static final String AAC_FILE_EXTENSION = ".aac"; - private static final String AC3_FILE_EXTENSION = ".ac3"; - private static final String EC3_FILE_EXTENSION = ".ec3"; - private static final String MP3_FILE_EXTENSION = ".mp3"; - private static final String MP4_FILE_EXTENSION = ".mp4"; - private static final String VTT_FILE_EXTENSION = ".vtt"; - private static final String WEBVTT_FILE_EXTENSION = ".webvtt"; - private final DataSource dataSource; private final TimestampAdjusterProvider timestampAdjusterProvider; private final HlsUrl[] variants; @@ -281,68 +264,10 @@ import java.util.Locale; if (previous != null && !switchingVariant) { startTimeUs = previous.getAdjustedEndTimeUs(); } - Format format = variants[newVariantIndex].format; - Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url); - // Set the extractor that will read the chunk. - Extractor extractor; - boolean needNewExtractor = previous == null - || previous.discontinuitySequenceNumber != segment.discontinuitySequenceNumber - || format != previous.trackFormat; - boolean extractorNeedsInit = true; - TimestampAdjuster timestampAdjuster = null; - String lastPathSegment = chunkUri.getLastPathSegment(); - if (lastPathSegment.endsWith(AAC_FILE_EXTENSION)) { - // TODO: Inject a timestamp adjuster and use it along with ID3 PRIV tag values with owner - // identifier com.apple.streaming.transportStreamTimestamp. This may also apply to the MP3 - // case below. - extractor = new AdtsExtractor(startTimeUs); - } else if (lastPathSegment.endsWith(AC3_FILE_EXTENSION) - || lastPathSegment.endsWith(EC3_FILE_EXTENSION)) { - extractor = new Ac3Extractor(startTimeUs); - } else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) { - extractor = new Mp3Extractor(startTimeUs); - } else if (lastPathSegment.endsWith(WEBVTT_FILE_EXTENSION) - || lastPathSegment.endsWith(VTT_FILE_EXTENSION)) { - timestampAdjuster = timestampAdjusterProvider.getAdjuster(segment.discontinuitySequenceNumber, - startTimeUs); - extractor = new WebvttExtractor(format.language, timestampAdjuster); - } else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION)) { - if (needNewExtractor) { - timestampAdjuster = timestampAdjusterProvider.getAdjuster( - segment.discontinuitySequenceNumber, startTimeUs); - extractor = new FragmentedMp4Extractor(0, timestampAdjuster); - } else { - extractorNeedsInit = false; - extractor = previous.extractor; - } - } else if (needNewExtractor) { - // MPEG-2 TS segments, but we need a new extractor. - timestampAdjuster = timestampAdjusterProvider.getAdjuster( - segment.discontinuitySequenceNumber, startTimeUs); - // This flag ensures the change of pid between streams does not affect the sample queues. - @DefaultTsPayloadReaderFactory.Flags - int esReaderFactoryFlags = 0; - String codecs = format.codecs; - if (!TextUtils.isEmpty(codecs)) { - // Sometimes AAC and H264 streams are declared in TS chunks even though they don't really - // exist. If we know from the codec attribute that they don't exist, then we can - // explicitly ignore them even if they're declared. - if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) { - esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM; - } - if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) { - esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM; - } - } - extractor = new TsExtractor(timestampAdjuster, - new DefaultTsPayloadReaderFactory(esReaderFactoryFlags), true); - } else { - // MPEG-2 TS segments, and we need to continue using the same extractor. - extractor = previous.extractor; - extractorNeedsInit = false; - } + TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster( + segment.discontinuitySequenceNumber, startTimeUs); DataSpec initDataSpec = null; Segment initSegment = mediaPlaylist.initializationSegment; @@ -356,9 +281,9 @@ import java.util.Locale; DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength, null); out.chunk = new HlsMediaChunk(dataSource, dataSpec, initDataSpec, variants[newVariantIndex], - trackSelection.getSelectionReason(), trackSelection.getSelectionData(), - segment, chunkMediaSequence, isTimestampMaster, timestampAdjuster, extractor, - extractorNeedsInit, switchingVariant, encryptionKey, encryptionIv); + trackSelection.getSelectionReason(), trackSelection.getSelectionData(), segment, + chunkMediaSequence, isTimestampMaster, timestampAdjuster, previous, encryptionKey, + encryptionIv); } /** diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java index 83343984b7..d0ad8d817f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java @@ -15,15 +15,23 @@ */ package com.google.android.exoplayer2.source.hls; +import android.text.TextUtils; import com.google.android.exoplayer2.extractor.DefaultExtractorInput; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.TimestampAdjuster; +import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; +import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor; +import com.google.android.exoplayer2.extractor.ts.Ac3Extractor; +import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; +import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; +import com.google.android.exoplayer2.extractor.ts.TsExtractor; import com.google.android.exoplayer2.source.chunk.MediaChunk; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; @@ -35,6 +43,14 @@ import java.util.concurrent.atomic.AtomicInteger; private static final AtomicInteger UID_SOURCE = new AtomicInteger(); + private static final String AAC_FILE_EXTENSION = ".aac"; + private static final String AC3_FILE_EXTENSION = ".ac3"; + private static final String EC3_FILE_EXTENSION = ".ec3"; + private static final String MP3_FILE_EXTENSION = ".mp3"; + private static final String MP4_FILE_EXTENSION = ".mp4"; + private static final String VTT_FILE_EXTENSION = ".vtt"; + private static final String WEBVTT_FILE_EXTENSION = ".webvtt"; + /** * A unique identifier for the chunk. */ @@ -45,11 +61,6 @@ import java.util.concurrent.atomic.AtomicInteger; */ public final int discontinuitySequenceNumber; - /** - * The extractor into which this chunk is being consumed. - */ - public final Extractor extractor; - /** * The url of the playlist from which this chunk was obtained. */ @@ -58,11 +69,11 @@ import java.util.concurrent.atomic.AtomicInteger; private final DataSource initDataSource; private final DataSpec initDataSpec; private final boolean isEncrypted; - private final boolean extractorNeedsInit; - private final boolean shouldSpliceIn; private final boolean isMasterTimestampSource; private final TimestampAdjuster timestampAdjuster; + private final HlsMediaChunk previousChunk; + private Extractor extractor; private int initSegmentBytesLoaded; private int bytesLoaded; private boolean initLoadCompleted; @@ -82,19 +93,14 @@ import java.util.concurrent.atomic.AtomicInteger; * @param chunkIndex The media sequence number of the chunk. * @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster. * @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number. - * @param extractor The extractor to decode samples from the data. - * @param extractorNeedsInit Whether the extractor needs initializing with the target - * {@link HlsSampleStreamWrapper}. - * @param shouldSpliceIn Whether the samples parsed from this chunk should be spliced into any - * samples already queued to the {@link HlsSampleStreamWrapper}. + * @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null. * @param encryptionKey For AES encryption chunks, the encryption key. * @param encryptionIv For AES encryption chunks, the encryption initialization vector. */ public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, DataSpec initDataSpec, HlsUrl hlsUrl, int trackSelectionReason, Object trackSelectionData, Segment segment, int chunkIndex, boolean isMasterTimestampSource, TimestampAdjuster timestampAdjuster, - Extractor extractor, boolean extractorNeedsInit, boolean shouldSpliceIn, byte[] encryptionKey, - byte[] encryptionIv) { + HlsMediaChunk previousChunk, byte[] encryptionKey, byte[] encryptionIv) { super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, hlsUrl.format, trackSelectionReason, trackSelectionData, segment.startTimeUs, segment.startTimeUs + segment.durationUs, chunkIndex); @@ -102,9 +108,7 @@ import java.util.concurrent.atomic.AtomicInteger; this.hlsUrl = hlsUrl; this.isMasterTimestampSource = isMasterTimestampSource; this.timestampAdjuster = timestampAdjuster; - this.extractor = extractor; - this.extractorNeedsInit = extractorNeedsInit; - this.shouldSpliceIn = shouldSpliceIn; + this.previousChunk = previousChunk; // Note: this.dataSource and dataSource may be different. this.isEncrypted = this.dataSource instanceof Aes128DataSource; initDataSource = dataSource; @@ -121,10 +125,7 @@ import java.util.concurrent.atomic.AtomicInteger; */ public void init(HlsSampleStreamWrapper output) { extractorOutput = output; - output.init(uid, shouldSpliceIn); - if (extractorNeedsInit) { - extractor.init(output); - } + output.init(uid, previousChunk != null && previousChunk.hlsUrl != hlsUrl); } /** @@ -165,6 +166,9 @@ import java.util.concurrent.atomic.AtomicInteger; @Override public void load() throws IOException, InterruptedException { + if (extractor == null) { + extractor = buildExtractor(); + } maybeLoadInitData(); if (!loadCanceled) { loadMedia(); @@ -173,8 +177,62 @@ import java.util.concurrent.atomic.AtomicInteger; // Private methods. + private Extractor buildExtractor() { + // Set the extractor that will read the chunk. + Extractor extractor; + boolean needNewExtractor = previousChunk == null + || previousChunk.discontinuitySequenceNumber != discontinuitySequenceNumber + || trackFormat != previousChunk.trackFormat; + boolean usingNewExtractor = true; + String lastPathSegment = dataSpec.uri.getLastPathSegment(); + if (lastPathSegment.endsWith(AAC_FILE_EXTENSION)) { + // TODO: Inject a timestamp adjuster and use it along with ID3 PRIV tag values with owner + // identifier com.apple.streaming.transportStreamTimestamp. This may also apply to the MP3 + // case below. + extractor = new AdtsExtractor(startTimeUs); + } else if (lastPathSegment.endsWith(AC3_FILE_EXTENSION) + || lastPathSegment.endsWith(EC3_FILE_EXTENSION)) { + extractor = new Ac3Extractor(startTimeUs); + } else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) { + extractor = new Mp3Extractor(startTimeUs); + } else if (lastPathSegment.endsWith(WEBVTT_FILE_EXTENSION) + || lastPathSegment.endsWith(VTT_FILE_EXTENSION)) { + extractor = new WebvttExtractor(trackFormat.language, timestampAdjuster); + } else if (!needNewExtractor) { + // Only reuse TS and fMP4 extractors. + usingNewExtractor = false; + extractor = previousChunk.extractor; + } else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION)) { + extractor = new FragmentedMp4Extractor(0, timestampAdjuster); + } else { + // MPEG-2 TS segments, but we need a new extractor. + // This flag ensures the change of pid between streams does not affect the sample queues. + @DefaultTsPayloadReaderFactory.Flags + int esReaderFactoryFlags = 0; + String codecs = trackFormat.codecs; + if (!TextUtils.isEmpty(codecs)) { + // Sometimes AAC and H264 streams are declared in TS chunks even though they don't really + // exist. If we know from the codec attribute that they don't exist, then we can + // explicitly ignore them even if they're declared. + if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) { + esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM; + } + if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) { + esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM; + } + } + extractor = new TsExtractor(timestampAdjuster, + new DefaultTsPayloadReaderFactory(esReaderFactoryFlags), true); + } + if (usingNewExtractor) { + extractor.init(extractorOutput); + } + return extractor; + } + private void maybeLoadInitData() throws IOException, InterruptedException { - if (!extractorNeedsInit || initLoadCompleted || initDataSpec == null) { + if (previousChunk == null || previousChunk.extractor != extractor || initLoadCompleted + || initDataSpec == null) { return; } DataSpec initSegmentDataSpec = Util.getRemainderDataSpec(initDataSpec, initSegmentBytesLoaded);