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
This commit is contained in:
aquilescanta 2016-11-25 10:26:05 -08:00 committed by Oliver Woodman
parent 2e3ffe1e94
commit c3c176d93c
2 changed files with 85 additions and 102 deletions

View File

@ -17,17 +17,9 @@ package com.google.android.exoplayer2.source.hls;
import android.net.Uri; import android.net.Uri;
import android.os.SystemClock; import android.os.SystemClock;
import android.text.TextUtils;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; 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.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.BehindLiveWindowException;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.Chunk; 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.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; 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.UriUtil;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; 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 DataSource dataSource;
private final TimestampAdjusterProvider timestampAdjusterProvider; private final TimestampAdjusterProvider timestampAdjusterProvider;
private final HlsUrl[] variants; private final HlsUrl[] variants;
@ -281,68 +264,10 @@ import java.util.Locale;
if (previous != null && !switchingVariant) { if (previous != null && !switchingVariant) {
startTimeUs = previous.getAdjustedEndTimeUs(); startTimeUs = previous.getAdjustedEndTimeUs();
} }
Format format = variants[newVariantIndex].format;
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url); Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
// Set the extractor that will read the chunk. TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(
Extractor extractor; segment.discontinuitySequenceNumber, startTimeUs);
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;
}
DataSpec initDataSpec = null; DataSpec initDataSpec = null;
Segment initSegment = mediaPlaylist.initializationSegment; Segment initSegment = mediaPlaylist.initializationSegment;
@ -356,9 +281,9 @@ import java.util.Locale;
DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength, DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength,
null); null);
out.chunk = new HlsMediaChunk(dataSource, dataSpec, initDataSpec, variants[newVariantIndex], out.chunk = new HlsMediaChunk(dataSource, dataSpec, initDataSpec, variants[newVariantIndex],
trackSelection.getSelectionReason(), trackSelection.getSelectionData(), trackSelection.getSelectionReason(), trackSelection.getSelectionData(), segment,
segment, chunkMediaSequence, isTimestampMaster, timestampAdjuster, extractor, chunkMediaSequence, isTimestampMaster, timestampAdjuster, previous, encryptionKey,
extractorNeedsInit, switchingVariant, encryptionKey, encryptionIv); encryptionIv);
} }
/** /**

View File

@ -15,15 +15,23 @@
*/ */
package com.google.android.exoplayer2.source.hls; package com.google.android.exoplayer2.source.hls;
import android.text.TextUtils;
import com.google.android.exoplayer2.extractor.DefaultExtractorInput; import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.TimestampAdjuster; 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.chunk.MediaChunk;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; 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.source.hls.playlist.HlsMediaPlaylist.Segment;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger; 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 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. * A unique identifier for the chunk.
*/ */
@ -45,11 +61,6 @@ import java.util.concurrent.atomic.AtomicInteger;
*/ */
public final int discontinuitySequenceNumber; 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. * 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 DataSource initDataSource;
private final DataSpec initDataSpec; private final DataSpec initDataSpec;
private final boolean isEncrypted; private final boolean isEncrypted;
private final boolean extractorNeedsInit;
private final boolean shouldSpliceIn;
private final boolean isMasterTimestampSource; private final boolean isMasterTimestampSource;
private final TimestampAdjuster timestampAdjuster; private final TimestampAdjuster timestampAdjuster;
private final HlsMediaChunk previousChunk;
private Extractor extractor;
private int initSegmentBytesLoaded; private int initSegmentBytesLoaded;
private int bytesLoaded; private int bytesLoaded;
private boolean initLoadCompleted; private boolean initLoadCompleted;
@ -82,19 +93,14 @@ import java.util.concurrent.atomic.AtomicInteger;
* @param chunkIndex The media sequence number of the chunk. * @param chunkIndex The media sequence number of the chunk.
* @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster. * @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster.
* @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number. * @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number.
* @param extractor The extractor to decode samples from the data. * @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null.
* @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 encryptionKey For AES encryption chunks, the encryption key. * @param encryptionKey For AES encryption chunks, the encryption key.
* @param encryptionIv For AES encryption chunks, the encryption initialization vector. * @param encryptionIv For AES encryption chunks, the encryption initialization vector.
*/ */
public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, DataSpec initDataSpec, public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, DataSpec initDataSpec,
HlsUrl hlsUrl, int trackSelectionReason, Object trackSelectionData, Segment segment, HlsUrl hlsUrl, int trackSelectionReason, Object trackSelectionData, Segment segment,
int chunkIndex, boolean isMasterTimestampSource, TimestampAdjuster timestampAdjuster, int chunkIndex, boolean isMasterTimestampSource, TimestampAdjuster timestampAdjuster,
Extractor extractor, boolean extractorNeedsInit, boolean shouldSpliceIn, byte[] encryptionKey, HlsMediaChunk previousChunk, byte[] encryptionKey, byte[] encryptionIv) {
byte[] encryptionIv) {
super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, hlsUrl.format, super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, hlsUrl.format,
trackSelectionReason, trackSelectionData, segment.startTimeUs, trackSelectionReason, trackSelectionData, segment.startTimeUs,
segment.startTimeUs + segment.durationUs, chunkIndex); segment.startTimeUs + segment.durationUs, chunkIndex);
@ -102,9 +108,7 @@ import java.util.concurrent.atomic.AtomicInteger;
this.hlsUrl = hlsUrl; this.hlsUrl = hlsUrl;
this.isMasterTimestampSource = isMasterTimestampSource; this.isMasterTimestampSource = isMasterTimestampSource;
this.timestampAdjuster = timestampAdjuster; this.timestampAdjuster = timestampAdjuster;
this.extractor = extractor; this.previousChunk = previousChunk;
this.extractorNeedsInit = extractorNeedsInit;
this.shouldSpliceIn = shouldSpliceIn;
// Note: this.dataSource and dataSource may be different. // Note: this.dataSource and dataSource may be different.
this.isEncrypted = this.dataSource instanceof Aes128DataSource; this.isEncrypted = this.dataSource instanceof Aes128DataSource;
initDataSource = dataSource; initDataSource = dataSource;
@ -121,10 +125,7 @@ import java.util.concurrent.atomic.AtomicInteger;
*/ */
public void init(HlsSampleStreamWrapper output) { public void init(HlsSampleStreamWrapper output) {
extractorOutput = output; extractorOutput = output;
output.init(uid, shouldSpliceIn); output.init(uid, previousChunk != null && previousChunk.hlsUrl != hlsUrl);
if (extractorNeedsInit) {
extractor.init(output);
}
} }
/** /**
@ -165,6 +166,9 @@ import java.util.concurrent.atomic.AtomicInteger;
@Override @Override
public void load() throws IOException, InterruptedException { public void load() throws IOException, InterruptedException {
if (extractor == null) {
extractor = buildExtractor();
}
maybeLoadInitData(); maybeLoadInitData();
if (!loadCanceled) { if (!loadCanceled) {
loadMedia(); loadMedia();
@ -173,8 +177,62 @@ import java.util.concurrent.atomic.AtomicInteger;
// Private methods. // 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 { private void maybeLoadInitData() throws IOException, InterruptedException {
if (!extractorNeedsInit || initLoadCompleted || initDataSpec == null) { if (previousChunk == null || previousChunk.extractor != extractor || initLoadCompleted
|| initDataSpec == null) {
return; return;
} }
DataSpec initSegmentDataSpec = Util.getRemainderDataSpec(initDataSpec, initSegmentBytesLoaded); DataSpec initSegmentDataSpec = Util.getRemainderDataSpec(initDataSpec, initSegmentBytesLoaded);