Commit 61014503 by aquilescanta Committed by Oliver Woodman

Merge initialization chunk into HlsMediaChunk

This will allow creating the extractor in the HlsMediaChunk.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=139915713
parent 9ac7f64c
...@@ -106,7 +106,6 @@ import java.util.Locale; ...@@ -106,7 +106,6 @@ import java.util.Locale;
private byte[] scratchSpace; private byte[] scratchSpace;
private IOException fatalError; private IOException fatalError;
private HlsInitializationChunk lastLoadedInitializationChunk;
private Uri encryptionKeyUri; private Uri encryptionKeyUri;
private byte[] encryptionKey; private byte[] encryptionKey;
private String encryptionIvString; private String encryptionIvString;
...@@ -266,20 +265,17 @@ import java.util.Locale; ...@@ -266,20 +265,17 @@ import java.util.Locale;
clearEncryptionData(); clearEncryptionData();
} }
// Compute start and end times, and the sequence number of the next chunk. // Compute start time and sequence number of the next chunk.
long startTimeUs = segment.startTimeUs; long startTimeUs = segment.startTimeUs;
if (previous != null && !switchingVariant) { if (previous != null && !switchingVariant) {
startTimeUs = previous.getAdjustedEndTimeUs(); startTimeUs = previous.getAdjustedEndTimeUs();
} }
long endTimeUs = startTimeUs + segment.durationUs;
Format format = variants[newVariantIndex].format; 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. // Set the extractor that will read the chunk.
Extractor extractor; Extractor extractor;
boolean useInitializedExtractor = lastLoadedInitializationChunk != null
&& lastLoadedInitializationChunk.format == format;
boolean needNewExtractor = previous == null boolean needNewExtractor = previous == null
|| previous.discontinuitySequenceNumber != segment.discontinuitySequenceNumber || previous.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|| format != previous.trackFormat; || format != previous.trackFormat;
...@@ -305,64 +301,56 @@ import java.util.Locale; ...@@ -305,64 +301,56 @@ import java.util.Locale;
} else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION)) { } else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION)) {
isTimestampMaster = true; isTimestampMaster = true;
if (needNewExtractor) { if (needNewExtractor) {
if (useInitializedExtractor) { timestampAdjuster = timestampAdjusterProvider.getAdjuster(
extractor = lastLoadedInitializationChunk.extractor; segment.discontinuitySequenceNumber, startTimeUs);
} else { extractor = new FragmentedMp4Extractor(0, timestampAdjuster);
timestampAdjuster = timestampAdjusterProvider.getAdjuster(
segment.discontinuitySequenceNumber, startTimeUs);
extractor = new FragmentedMp4Extractor(0, timestampAdjuster);
}
} else { } else {
extractorNeedsInit = false;
extractor = previous.extractor; extractor = previous.extractor;
} }
} else if (needNewExtractor) { } else if (needNewExtractor) {
// MPEG-2 TS segments, but we need a new extractor. // MPEG-2 TS segments, but we need a new extractor.
isTimestampMaster = true; isTimestampMaster = true;
if (useInitializedExtractor) { timestampAdjuster = timestampAdjusterProvider.getAdjuster(
extractor = lastLoadedInitializationChunk.extractor; segment.discontinuitySequenceNumber, startTimeUs);
} else { // This flag ensures the change of pid between streams does not affect the sample queues.
timestampAdjuster = timestampAdjusterProvider.getAdjuster( @DefaultTsPayloadReaderFactory.Flags
segment.discontinuitySequenceNumber, startTimeUs); int esReaderFactoryFlags = 0;
// This flag ensures the change of pid between streams does not affect the sample queues. String codecs = format.codecs;
@DefaultTsPayloadReaderFactory.Flags if (!TextUtils.isEmpty(codecs)) {
int esReaderFactoryFlags = 0; // Sometimes AAC and H264 streams are declared in TS chunks even though they don't really
String codecs = format.codecs; // exist. If we know from the codec attribute that they don't exist, then we can
if (!TextUtils.isEmpty(codecs)) { // explicitly ignore them even if they're declared.
// Sometimes AAC and H264 streams are declared in TS chunks even though they don't really if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) {
// exist. If we know from the codec attribute that they don't exist, then we can esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM;
// explicitly ignore them even if they're declared. }
if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) { if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) {
esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM; esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM;
}
if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) {
esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM;
}
} }
extractor = new TsExtractor(timestampAdjuster,
new DefaultTsPayloadReaderFactory(esReaderFactoryFlags), true);
} }
extractor = new TsExtractor(timestampAdjuster,
new DefaultTsPayloadReaderFactory(esReaderFactoryFlags), true);
} else { } else {
// MPEG-2 TS segments, and we need to continue using the same extractor. // MPEG-2 TS segments, and we need to continue using the same extractor.
extractor = previous.extractor; extractor = previous.extractor;
extractorNeedsInit = false; extractorNeedsInit = false;
} }
// Initialize the extractor. DataSpec initDataSpec = null;
if (needNewExtractor && mediaPlaylist.initializationSegment != null Segment initSegment = mediaPlaylist.initializationSegment;
&& !useInitializedExtractor) { if (initSegment != null) {
out.chunk = buildInitializationChunk(mediaPlaylist, extractor, format); Uri initSegmentUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, initSegment.url);
return; initDataSpec = new DataSpec(initSegmentUri, initSegment.byterangeOffset,
initSegment.byterangeLength, null);
} }
lastLoadedInitializationChunk = null;
// Configure the data source and spec for the chunk. // Configure the data source and spec for the chunk.
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, variants[newVariantIndex], out.chunk = new HlsMediaChunk(dataSource, dataSpec, initDataSpec, variants[newVariantIndex],
trackSelection.getSelectionReason(), trackSelection.getSelectionData(), trackSelection.getSelectionReason(), trackSelection.getSelectionData(),
startTimeUs, endTimeUs, chunkMediaSequence, segment.discontinuitySequenceNumber, segment, chunkMediaSequence, isTimestampMaster, timestampAdjuster, extractor,
isTimestampMaster, timestampAdjuster, extractor, extractorNeedsInit, switchingVariant, extractorNeedsInit, switchingVariant, encryptionKey, encryptionIv);
encryptionKey, encryptionIv);
} }
/** /**
...@@ -376,9 +364,6 @@ import java.util.Locale; ...@@ -376,9 +364,6 @@ import java.util.Locale;
HlsMediaChunk mediaChunk = (HlsMediaChunk) chunk; HlsMediaChunk mediaChunk = (HlsMediaChunk) chunk;
playlistTracker.onChunkLoaded(mediaChunk.hlsUrl, mediaChunk.chunkIndex, playlistTracker.onChunkLoaded(mediaChunk.hlsUrl, mediaChunk.chunkIndex,
mediaChunk.getAdjustedStartTimeUs()); mediaChunk.getAdjustedStartTimeUs());
}
if (chunk instanceof HlsInitializationChunk) {
lastLoadedInitializationChunk = (HlsInitializationChunk) chunk;
} else if (chunk instanceof EncryptionKeyChunk) { } else if (chunk instanceof EncryptionKeyChunk) {
EncryptionKeyChunk encryptionKeyChunk = (EncryptionKeyChunk) chunk; EncryptionKeyChunk encryptionKeyChunk = (EncryptionKeyChunk) chunk;
scratchSpace = encryptionKeyChunk.getDataHolder(); scratchSpace = encryptionKeyChunk.getDataHolder();
...@@ -419,18 +404,6 @@ import java.util.Locale; ...@@ -419,18 +404,6 @@ import java.util.Locale;
// Private methods. // Private methods.
private HlsInitializationChunk buildInitializationChunk(HlsMediaPlaylist mediaPlaylist,
Extractor extractor, Format format) {
Segment initSegment = mediaPlaylist.initializationSegment;
// The initialization segment is required before the actual media chunk.
Uri initSegmentUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, initSegment.url);
DataSpec initDataSpec = new DataSpec(initSegmentUri, initSegment.byterangeOffset,
initSegment.byterangeLength, null);
return new HlsInitializationChunk(dataSource, initDataSpec,
trackSelection.getSelectionReason(), trackSelection.getSelectionData(), extractor,
format);
}
private EncryptionKeyChunk newEncryptionKeyChunk(Uri keyUri, String iv, int variantIndex, private EncryptionKeyChunk newEncryptionKeyChunk(Uri keyUri, String iv, int variantIndex,
int trackSelectionReason, Object trackSelectionData) { int trackSelectionReason, Object trackSelectionData) {
DataSpec dataSpec = new DataSpec(keyUri, 0, C.LENGTH_UNSET, null, DataSpec.FLAG_ALLOW_GZIP); DataSpec dataSpec = new DataSpec(keyUri, 0, C.LENGTH_UNSET, null, DataSpec.FLAG_ALLOW_GZIP);
......
/*
* 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.source.hls;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
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.source.chunk.Chunk;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
/**
* An HLS initialization chunk. Provides the extractor with information required for extracting the
* samples.
*/
/* package */ final class HlsInitializationChunk extends Chunk {
public final Format format;
public final Extractor extractor;
private int bytesLoaded;
private volatile boolean loadCanceled;
public HlsInitializationChunk(DataSource dataSource, DataSpec dataSpec, int trackSelectionReason,
Object trackSelectionData, Extractor extractor, Format format) {
super(dataSource, dataSpec, C.TRACK_TYPE_DEFAULT, null, trackSelectionReason,
trackSelectionData, C.TIME_UNSET, C.TIME_UNSET);
this.extractor = extractor;
this.format = format;
}
/**
* Sets the {@link HlsSampleStreamWrapper} that will receive the sample format information from
* the initialization chunk.
*
* @param output The output that will receive the format information.
*/
public void init(HlsSampleStreamWrapper output) {
extractor.init(output);
}
@Override
public long bytesLoaded() {
return bytesLoaded;
}
@Override
public void cancelLoad() {
loadCanceled = true;
}
@Override
public boolean isLoadCanceled() {
return loadCanceled;
}
@Override
public void load() throws IOException, InterruptedException {
DataSpec loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded);
try {
ExtractorInput input = new DefaultExtractorInput(dataSource,
loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
try {
int result = Extractor.RESULT_CONTINUE;
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
result = extractor.read(input, null);
}
} finally {
bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition);
}
} finally {
dataSource.close();
}
}
}
...@@ -21,6 +21,7 @@ import com.google.android.exoplayer2.extractor.ExtractorInput; ...@@ -21,6 +21,7 @@ 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.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.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.Util; import com.google.android.exoplayer2.util.Util;
...@@ -54,13 +55,17 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -54,13 +55,17 @@ import java.util.concurrent.atomic.AtomicInteger;
*/ */
public final HlsUrl hlsUrl; public final HlsUrl hlsUrl;
private final DataSource initDataSource;
private final DataSpec initDataSpec;
private final boolean isEncrypted; private final boolean isEncrypted;
private final boolean extractorNeedsInit; private final boolean extractorNeedsInit;
private final boolean shouldSpliceIn; private final boolean shouldSpliceIn;
private final boolean isMasterTimestampSource; private final boolean isMasterTimestampSource;
private final TimestampAdjuster timestampAdjuster; private final TimestampAdjuster timestampAdjuster;
private int initSegmentBytesLoaded;
private int bytesLoaded; private int bytesLoaded;
private boolean initLoadCompleted;
private HlsSampleStreamWrapper extractorOutput; private HlsSampleStreamWrapper extractorOutput;
private long adjustedEndTimeUs; private long adjustedEndTimeUs;
private volatile boolean loadCanceled; private volatile boolean loadCanceled;
...@@ -69,13 +74,12 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -69,13 +74,12 @@ import java.util.concurrent.atomic.AtomicInteger;
/** /**
* @param dataSource The source from which the data should be loaded. * @param dataSource The source from which the data should be loaded.
* @param dataSpec Defines the data to be loaded. * @param dataSpec Defines the data to be loaded.
* @param initDataSpec Defines the initialization data to be fed to new extractors. May be null.
* @param hlsUrl The url of the playlist from which this chunk was obtained. * @param hlsUrl The url of the playlist from which this chunk was obtained.
* @param trackSelectionReason See {@link #trackSelectionReason}. * @param trackSelectionReason See {@link #trackSelectionReason}.
* @param trackSelectionData See {@link #trackSelectionData}. * @param trackSelectionData See {@link #trackSelectionData}.
* @param startTimeUs The start time of the media contained by the chunk, in microseconds. * @param segment The {@link Segment} for which this media chunk is created.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param chunkIndex The media sequence number of the chunk. * @param chunkIndex The media sequence number of the chunk.
* @param discontinuitySequenceNumber The discontinuity 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 extractor The extractor to decode samples from the data.
...@@ -86,23 +90,26 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -86,23 +90,26 @@ import java.util.concurrent.atomic.AtomicInteger;
* @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, HlsUrl hlsUrl, public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, DataSpec initDataSpec,
int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs, HlsUrl hlsUrl, int trackSelectionReason, Object trackSelectionData, Segment segment,
int chunkIndex, int discontinuitySequenceNumber, boolean isMasterTimestampSource, int chunkIndex, boolean isMasterTimestampSource, TimestampAdjuster timestampAdjuster,
TimestampAdjuster timestampAdjuster, Extractor extractor, boolean extractorNeedsInit, Extractor extractor, boolean extractorNeedsInit, boolean shouldSpliceIn, byte[] encryptionKey,
boolean shouldSpliceIn, byte[] encryptionKey, byte[] encryptionIv) { byte[] encryptionIv) {
super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, hlsUrl.format, super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, hlsUrl.format,
trackSelectionReason, trackSelectionData, startTimeUs, endTimeUs, chunkIndex); trackSelectionReason, trackSelectionData, segment.startTimeUs,
segment.startTimeUs + segment.durationUs, chunkIndex);
this.initDataSpec = initDataSpec;
this.hlsUrl = hlsUrl; this.hlsUrl = hlsUrl;
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
this.isMasterTimestampSource = isMasterTimestampSource; this.isMasterTimestampSource = isMasterTimestampSource;
this.timestampAdjuster = timestampAdjuster; this.timestampAdjuster = timestampAdjuster;
this.extractor = extractor; this.extractor = extractor;
this.extractorNeedsInit = extractorNeedsInit; this.extractorNeedsInit = extractorNeedsInit;
this.shouldSpliceIn = shouldSpliceIn; this.shouldSpliceIn = shouldSpliceIn;
// Note: this.dataSource and dataSource may be different. // Note: this.dataSource and dataSource may be different.
adjustedEndTimeUs = endTimeUs;
this.isEncrypted = this.dataSource instanceof Aes128DataSource; this.isEncrypted = this.dataSource instanceof Aes128DataSource;
initDataSource = dataSource;
discontinuitySequenceNumber = segment.discontinuitySequenceNumber;
adjustedEndTimeUs = endTimeUs;
uid = UID_SOURCE.getAndIncrement(); uid = UID_SOURCE.getAndIncrement();
} }
...@@ -158,6 +165,37 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -158,6 +165,37 @@ import java.util.concurrent.atomic.AtomicInteger;
@Override @Override
public void load() throws IOException, InterruptedException { public void load() throws IOException, InterruptedException {
maybeLoadInitData();
if (!loadCanceled) {
loadMedia();
}
}
// Private methods.
private void maybeLoadInitData() throws IOException, InterruptedException {
if (!extractorNeedsInit || initLoadCompleted || initDataSpec == null) {
return;
}
DataSpec initSegmentDataSpec = Util.getRemainderDataSpec(initDataSpec, initSegmentBytesLoaded);
try {
ExtractorInput input = new DefaultExtractorInput(initDataSource,
initSegmentDataSpec.absoluteStreamPosition, initDataSource.open(initSegmentDataSpec));
try {
int result = Extractor.RESULT_CONTINUE;
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
result = extractor.read(input, null);
}
} finally {
initSegmentBytesLoaded += (int) (input.getPosition() - dataSpec.absoluteStreamPosition);
}
} finally {
Util.closeQuietly(dataSource);
}
initLoadCompleted = true;
}
private void loadMedia() throws IOException, InterruptedException {
// If we previously fed part of this chunk to the extractor, we need to skip it this time. For // If we previously fed part of this chunk to the extractor, we need to skip it this time. For
// encrypted content we need to skip the data by reading it through the source, so as to ensure // encrypted content we need to skip the data by reading it through the source, so as to ensure
// correct decryption of the remainder of the chunk. For clear content, we can request the // correct decryption of the remainder of the chunk. For clear content, we can request the
...@@ -193,13 +231,11 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -193,13 +231,11 @@ import java.util.concurrent.atomic.AtomicInteger;
bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition); bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition);
} }
} finally { } finally {
dataSource.close(); Util.closeQuietly(dataSource);
} }
loadCompleted = true; loadCompleted = true;
} }
// Private methods
/** /**
* If the content is encrypted, returns an {@link Aes128DataSource} that wraps the original in * If the content is encrypted, returns an {@link Aes128DataSource} that wraps the original in
* order to decrypt the loaded data. Else returns the original. * order to decrypt the loaded data. Else returns the original.
......
...@@ -358,8 +358,6 @@ import java.util.LinkedList; ...@@ -358,8 +358,6 @@ import java.util.LinkedList;
HlsMediaChunk mediaChunk = (HlsMediaChunk) loadable; HlsMediaChunk mediaChunk = (HlsMediaChunk) loadable;
mediaChunk.init(this); mediaChunk.init(this);
mediaChunks.add(mediaChunk); mediaChunks.add(mediaChunk);
} else if (loadable instanceof HlsInitializationChunk) {
((HlsInitializationChunk) loadable).init(this);
} }
long elapsedRealtimeMs = loader.startLoading(loadable, this, minLoadableRetryCount); long elapsedRealtimeMs = loader.startLoading(loadable, this, minLoadableRetryCount);
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment