Clean up max dimension handling.

This commit is contained in:
Oliver Woodman 2015-09-01 14:17:21 +01:00
parent 4a9ff7b094
commit cb85dc25aa
12 changed files with 83 additions and 144 deletions

View File

@ -18,7 +18,6 @@ package com.google.android.exoplayer.dash;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.TimeRange;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
@ -85,23 +84,14 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
private static final Format WIDE_VIDEO =
new Format("3", "video/mp4", WIDE_WIDTH, 50, -1, -1, -1, 1000);
@Mock private DataSource mockDataSource;
@Mock
private DataSource mockDataSource;
@Override
public void setUp() throws Exception {
TestUtil.setUpMockito(this);
}
public void testMaxVideoDimensions() {
DashChunkSource chunkSource = new DashChunkSource(generateVodMpd(), AdaptationSet.TYPE_VIDEO,
null, null, null);
MediaFormat format = MediaFormat.createVideoFormat("video/h264", 5000, 1, 1, 1, 1, 1, null);
format = chunkSource.getWithMaxVideoDimensions(format);
assertEquals(WIDE_WIDTH, format.maxWidth);
assertEquals(TALL_HEIGHT, format.maxHeight);
}
public void testGetAvailableRangeOnVod() {
DashChunkSource chunkSource = new DashChunkSource(generateVodMpd(), AdaptationSet.TYPE_VIDEO,
null, null, mock(FormatEvaluator.class));
@ -192,22 +182,6 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
checkSegmentRequestSequenceOnMultiPeriodLive(chunkSource);
}
public void testMaxVideoDimensionsLegacy() {
SingleSegmentBase segmentBase1 = new SingleSegmentBase("https://example.com/1.mp4");
Representation representation1 =
Representation.newInstance(0, 0, null, 0, TALL_VIDEO, segmentBase1);
SingleSegmentBase segmentBase2 = new SingleSegmentBase("https://example.com/2.mp4");
Representation representation2 =
Representation.newInstance(0, 0, null, 0, WIDE_VIDEO, segmentBase2);
DashChunkSource chunkSource = new DashChunkSource(null, null, representation1, representation2);
MediaFormat format = MediaFormat.createVideoFormat("video/h264", 5000, 1, 1, 1, 1, 1, null);
format = chunkSource.getWithMaxVideoDimensions(format);
assertEquals(WIDE_WIDTH, format.maxWidth);
assertEquals(TALL_HEIGHT, format.maxHeight);
}
public void testLiveEdgeNoLatency() {
long startTimeMs = 0;

View File

@ -323,25 +323,12 @@ public final class MediaFormat {
if (obj == null || getClass() != obj.getClass()) {
return false;
}
return equalsInternal((MediaFormat) obj, false);
}
public boolean equals(MediaFormat other, boolean ignoreMaxDimensions) {
if (this == other) {
return true;
}
if (other == null) {
return false;
}
return equalsInternal(other, ignoreMaxDimensions);
}
private boolean equalsInternal(MediaFormat other, boolean ignoreMaxDimensions) {
MediaFormat other = (MediaFormat) obj;
if (adaptive != other.adaptive || bitrate != other.bitrate || maxInputSize != other.maxInputSize
|| width != other.width || height != other.height
|| rotationDegrees != other.rotationDegrees
|| pixelWidthHeightRatio != other.pixelWidthHeightRatio
|| (!ignoreMaxDimensions && (maxWidth != other.maxWidth || maxHeight != other.maxHeight))
|| maxWidth != other.maxWidth || maxHeight != other.maxHeight
|| channelCount != other.channelCount || sampleRate != other.sampleRate
|| !Util.areEqual(language, other.language) || !Util.areEqual(mimeType, other.mimeType)
|| initializationData.size() != other.initializationData.size()) {

View File

@ -232,8 +232,7 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
if (haveSamples || currentChunk.isMediaFormatFinal) {
MediaFormat mediaFormat = currentChunk.getMediaFormat();
if (!mediaFormat.equals(downstreamMediaFormat, true)) {
mediaFormat = chunkSource.getWithMaxVideoDimensions(mediaFormat);
if (!mediaFormat.equals(downstreamMediaFormat)) {
formatHolder.format = mediaFormat;
formatHolder.drmInitData = currentChunk.getDrmInitData();
downstreamMediaFormat = mediaFormat;

View File

@ -76,19 +76,6 @@ public interface ChunkSource {
*/
void enable(int track);
/**
* Adaptive video {@link ChunkSource} implementations must return a copy of the provided
* {@link MediaFormat} with the maximum video dimensions set. Other implementations can return
* the provided {@link MediaFormat} directly.
* <p>
* This method should only be called when the source is enabled.
*
* @param format The format to be copied or returned.
* @return A copy of the provided {@link MediaFormat} with the maximum video dimensions set, or
* the provided format.
*/
MediaFormat getWithMaxVideoDimensions(MediaFormat format);
/**
* Indicates to the source that it should still be checking for updates to the stream.
* <p>

View File

@ -36,6 +36,8 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
private final ChunkExtractorWrapper extractorWrapper;
private final long sampleOffsetUs;
private final int adaptiveMaxWidth;
private final int adaptiveMaxHeight;
private MediaFormat mediaFormat;
private DrmInitData drmInitData;
@ -56,6 +58,12 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
* @param extractorWrapper A wrapped extractor to use for parsing the data.
* @param mediaFormat The {@link MediaFormat} of the chunk, if known. May be null if the data is
* known to define its own format.
* @param adaptiveMaxWidth If this chunk contains video and is part of an adaptive playback, this
* is the maximum width of the video in pixels that will be encountered during the playback.
* {@link MediaFormat#NO_VALUE} otherwise.
* @param adaptiveMaxHeight If this chunk contains video and is part of an adaptive playback, this
* is the maximum height of the video in pixels that will be encountered during the playback.
* {@link MediaFormat#NO_VALUE} otherwise.
* @param drmInitData The {@link DrmInitData} for the chunk. Null if the media is not drm
* protected. May also be null if the data is known to define its own initialization data.
* @param isMediaFormatFinal True if {@code mediaFormat} and {@code drmInitData} are known to be
@ -64,13 +72,16 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
*/
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, boolean isLastChunk, long sampleOffsetUs,
ChunkExtractorWrapper extractorWrapper, MediaFormat mediaFormat, DrmInitData drmInitData,
boolean isMediaFormatFinal, int parentId) {
ChunkExtractorWrapper extractorWrapper, MediaFormat mediaFormat, int adaptiveMaxWidth,
int adaptiveMaxHeight, DrmInitData drmInitData, boolean isMediaFormatFinal, int parentId) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, isLastChunk,
isMediaFormatFinal, parentId);
this.extractorWrapper = extractorWrapper;
this.sampleOffsetUs = sampleOffsetUs;
this.mediaFormat = getAdjustedMediaFormat(mediaFormat, sampleOffsetUs);
this.adaptiveMaxWidth = adaptiveMaxWidth;
this.adaptiveMaxHeight = adaptiveMaxHeight;
this.mediaFormat = getAdjustedMediaFormat(mediaFormat, sampleOffsetUs, adaptiveMaxWidth,
adaptiveMaxHeight);
this.drmInitData = drmInitData;
}
@ -103,7 +114,8 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
@Override
public final void format(MediaFormat mediaFormat) {
this.mediaFormat = getAdjustedMediaFormat(mediaFormat, sampleOffsetUs);
this.mediaFormat = getAdjustedMediaFormat(mediaFormat, sampleOffsetUs, adaptiveMaxWidth,
adaptiveMaxHeight);
}
@Override
@ -163,10 +175,16 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
// Private methods.
private static MediaFormat getAdjustedMediaFormat(MediaFormat format, long sampleOffsetUs) {
if (sampleOffsetUs != 0 && format != null
&& format.subsampleOffsetUs != MediaFormat.OFFSET_SAMPLE_RELATIVE) {
return format.copyWithSubsampleOffsetUs(format.subsampleOffsetUs + sampleOffsetUs);
private static MediaFormat getAdjustedMediaFormat(MediaFormat format, long sampleOffsetUs,
int adaptiveMaxWidth, int adaptiveMaxHeight) {
if (format == null) {
return null;
}
if (sampleOffsetUs != 0 && format.subsampleOffsetUs != MediaFormat.OFFSET_SAMPLE_RELATIVE) {
format = format.copyWithSubsampleOffsetUs(format.subsampleOffsetUs + sampleOffsetUs);
}
if (adaptiveMaxWidth != MediaFormat.NO_VALUE || adaptiveMaxHeight != MediaFormat.NO_VALUE) {
format = format.copyWithMaxVideoDimensions(adaptiveMaxWidth, adaptiveMaxHeight);
}
return format;
}

View File

@ -108,11 +108,6 @@ public final class MultiTrackChunkSource implements ChunkSource, ExoPlayerCompon
selectedSource.maybeThrowError();
}
@Override
public MediaFormat getWithMaxVideoDimensions(MediaFormat format) {
return selectedSource.getWithMaxVideoDimensions(format);
}
@Override
public void handleMessage(int what, Object msg) throws ExoPlaybackException {
Assertions.checkState(!enabled);

View File

@ -69,11 +69,6 @@ public final class SingleSampleChunkSource implements ChunkSource {
return mediaFormat;
}
@Override
public MediaFormat getWithMaxVideoDimensions(MediaFormat format) {
return format;
}
@Override
public void enable(int track) {
// Do nothing.

View File

@ -271,11 +271,10 @@ public class DashChunkSource implements ChunkSource {
processManifest(currentManifest);
String mimeType = "";
long totalDurationUs = 0;
int maxHeight = 0;
int maxWidth = 0;
int maxHeight = 0;
String mimeType = "";
for (int i = 0; i < periodHolders.size(); i++) {
PeriodHolder periodHolder = periodHolders.valueAt(i);
if (totalDurationUs != TrackRenderer.UNKNOWN_TIME_US) {
@ -285,21 +284,15 @@ public class DashChunkSource implements ChunkSource {
totalDurationUs += periodHolder.durationUs;
}
}
mimeType = periodHolder.mimeType;
maxHeight = Math.max(maxHeight, periodHolder.maxHeight);
maxWidth = Math.max(maxWidth, periodHolder.maxWidth);
maxHeight = Math.max(maxHeight, periodHolder.maxHeight);
mimeType = periodHolder.mimeType;
}
this.maxWidth = maxWidth == 0 ? MediaFormat.NO_VALUE : maxWidth;
this.maxHeight = maxHeight == 0 ? MediaFormat.NO_VALUE : maxHeight;
// TODO: Remove this and pass proper formats instead (b/22996976).
this.mediaFormat = MediaFormat.createFormatForMimeType(mimeType, MediaFormat.NO_VALUE,
totalDurationUs);
this.maxHeight = maxHeight;
this.maxWidth = maxWidth;
}
@Override
public final MediaFormat getWithMaxVideoDimensions(MediaFormat format) {
return MimeTypes.isVideo(mediaFormat.mimeType)
? format.copyWithMaxVideoDimensions(maxWidth, maxHeight) : format;
}
@Override
@ -606,8 +599,8 @@ public class DashChunkSource implements ChunkSource {
boolean isMediaFormatFinal = (mediaFormat != null);
return new ContainerMediaChunk(dataSource, dataSpec, trigger, representation.format,
startTimeUs, endTimeUs, segmentNum, isLastSegment, sampleOffsetUs,
representationHolder.extractorWrapper, mediaFormat, drmInitData, isMediaFormatFinal,
periodHolder.manifestIndex);
representationHolder.extractorWrapper, mediaFormat, maxWidth, maxHeight, drmInitData,
isMediaFormatFinal, periodHolder.manifestIndex);
}
}
@ -854,8 +847,8 @@ public class DashChunkSource implements ChunkSource {
formats = new Format[representationCount];
representationHolders = new HashMap<>(representationCount);
int maxWidth = -1;
int maxHeight = -1;
int maxWidth = 0;
int maxHeight = 0;
String mimeType = "";
for (int i = 0; i < representationCount; i++) {
int representationIndex = representationIndices != null ? representationIndices[i] : i;

View File

@ -119,8 +119,8 @@ public class HlsChunkSource {
private final BandwidthMeter bandwidthMeter;
private final int adaptiveMode;
private final String baseUri;
private final int maxWidth;
private final int maxHeight;
private final int adaptiveMaxWidth;
private final int adaptiveMaxHeight;
private final long minBufferDurationToSwitchUpUs;
private final long maxBufferDurationToSwitchDownUs;
@ -184,8 +184,8 @@ public class HlsChunkSource {
variantBlacklistTimes = new long[1];
setMediaPlaylist(0, (HlsMediaPlaylist) playlist);
// We won't be adapting between different variants.
maxWidth = -1;
maxHeight = -1;
adaptiveMaxWidth = MediaFormat.NO_VALUE;
adaptiveMaxHeight = MediaFormat.NO_VALUE;
} else {
List<Variant> masterPlaylistVariants = ((HlsMasterPlaylist) playlist).variants;
variants = buildOrderedVariants(masterPlaylistVariants, variantIndices);
@ -208,13 +208,13 @@ public class HlsChunkSource {
}
if (variants.length <= 1 || adaptiveMode == ADAPTIVE_MODE_NONE) {
// We won't be adapting between different variants.
this.maxWidth = -1;
this.maxHeight = -1;
this.adaptiveMaxWidth = MediaFormat.NO_VALUE;
this.adaptiveMaxHeight = MediaFormat.NO_VALUE;
} else {
// We will be adapting between different variants.
// TODO: We should allow the default values to be passed through the constructor.
this.maxWidth = maxWidth > 0 ? maxWidth : 1920;
this.maxHeight = maxHeight > 0 ? maxHeight : 1080;
this.adaptiveMaxWidth = maxWidth > 0 ? maxWidth : 1920;
this.adaptiveMaxHeight = maxHeight > 0 ? maxHeight : 1080;
}
}
}
@ -223,20 +223,6 @@ public class HlsChunkSource {
return live ? C.UNKNOWN_TIME_US : durationUs;
}
/**
* Adaptive implementations must return a copy of the provided {@link MediaFormat} with the
* maximum video dimensions set. Other implementations can return the provided {@link MediaFormat}
* directly.
*
* @param format The format to be copied or returned.
* @return A copy of the provided {@link MediaFormat} with the maximum video dimensions set, or
* the provided format.
*/
public MediaFormat getWithMaxVideoDimensions(MediaFormat format) {
return (maxWidth == -1 || maxHeight == -1) ? format
: format.copyWithMaxVideoDimensions(maxWidth, maxHeight);
}
/**
* Returns the next {@link Chunk} that should be loaded.
*
@ -348,7 +334,7 @@ public class HlsChunkSource {
Extractor extractor = chunkUri.getLastPathSegment().endsWith(AAC_FILE_EXTENSION)
? new AdtsExtractor(startTimeUs) : new TsExtractor(startTimeUs);
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
switchingVariantSpliced);
switchingVariantSpliced, adaptiveMaxWidth, adaptiveMaxHeight);
} else {
extractorWrapper = previousTsChunk.extractorWrapper;
}

View File

@ -27,6 +27,7 @@ import com.google.android.exoplayer.extractor.SeekMap;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes;
import android.util.SparseArray;
@ -44,7 +45,10 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
private final Extractor extractor;
private final SparseArray<DefaultTrackOutput> sampleQueues;
private final boolean shouldSpliceIn;
private final int adaptiveMaxWidth;
private final int adaptiveMaxHeight;
private MediaFormat[] sampleQueueFormats;
private Allocator allocator;
private volatile boolean tracksBuilt;
@ -54,12 +58,14 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
private boolean spliceConfigured;
public HlsExtractorWrapper(int trigger, Format format, long startTimeUs, Extractor extractor,
boolean shouldSpliceIn) {
boolean shouldSpliceIn, int adaptiveMaxWidth, int adaptiveMaxHeight) {
this.trigger = trigger;
this.format = format;
this.startTimeUs = startTimeUs;
this.extractor = extractor;
this.shouldSpliceIn = shouldSpliceIn;
this.adaptiveMaxWidth = adaptiveMaxWidth;
this.adaptiveMaxHeight = adaptiveMaxHeight;
sampleQueues = new SparseArray<>();
}
@ -86,6 +92,15 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
}
}
prepared = true;
sampleQueueFormats = new MediaFormat[sampleQueues.size()];
for (int i = 0; i < sampleQueueFormats.length; i++) {
MediaFormat format = sampleQueues.valueAt(i).getFormat();
if (MimeTypes.isVideo(format.mimeType) && (adaptiveMaxWidth != MediaFormat.NO_VALUE
|| adaptiveMaxHeight != MediaFormat.NO_VALUE)) {
format = format.copyWithMaxVideoDimensions(adaptiveMaxWidth, adaptiveMaxHeight);
}
sampleQueueFormats[i] = format;
}
}
return prepared;
}
@ -169,7 +184,7 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
*/
public MediaFormat getMediaFormat(int track) {
Assertions.checkState(isPrepared());
return sampleQueues.valueAt(track).getFormat();
return sampleQueueFormats[track];
}
/**

View File

@ -138,11 +138,11 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
mediaFormats = new MediaFormat[trackCount];
long durationUs = chunkSource.getDurationUs();
for (int i = 0; i < trackCount; i++) {
mediaFormats[i] = extractor.getMediaFormat(i).copyWithDurationUs(durationUs);
if (MimeTypes.isVideo(mediaFormats[i].mimeType)) {
mediaFormats[i] = chunkSource.getWithMaxVideoDimensions(mediaFormats[i])
.copyAsAdaptive();
MediaFormat format = extractor.getMediaFormat(i).copyWithDurationUs(durationUs);
if (MimeTypes.isVideo(format.mimeType)) {
format = format.copyAsAdaptive();
}
mediaFormats[i] = format;
}
prepared = true;
return true;
@ -294,8 +294,7 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
}
MediaFormat mediaFormat = extractor.getMediaFormat(track);
if (mediaFormat != null && !mediaFormat.equals(downstreamMediaFormats[track], true)) {
mediaFormat = chunkSource.getWithMaxVideoDimensions(mediaFormat);
if (mediaFormat != null && !mediaFormat.equals(downstreamMediaFormats[track])) {
formatHolder.format = mediaFormat;
downstreamMediaFormats[track] = mediaFormat;
return FORMAT_READ;

View File

@ -196,15 +196,6 @@ public class SmoothStreamingChunkSource implements ChunkSource,
}
}
@Override
public final MediaFormat getWithMaxVideoDimensions(MediaFormat format) {
if (enabledTrack.isAdaptive() && MimeTypes.isVideo(format.mimeType)) {
return format.copyWithMaxVideoDimensions(
enabledTrack.adaptiveMaxWidth, enabledTrack.adaptiveMaxHeight);
}
return format;
}
@Override
public void continueBuffering(long playbackPositionUs) {
if (manifestFetcher == null || !currentManifest.isLive || fatalError != null) {
@ -323,10 +314,10 @@ public class SmoothStreamingChunkSource implements ChunkSource,
int manifestTrackKey = getManifestTrackKey(enabledTrack.elementIndex, manifestTrackIndex);
Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex);
Chunk mediaChunk = newMediaChunk(selectedFormat, uri, null,
extractorWrappers.get(manifestTrackKey),
drmInitData, dataSource, currentAbsoluteChunkIndex, isLastChunk, chunkStartTimeUs,
chunkEndTimeUs, evaluation.trigger,
mediaFormats.get(manifestTrackKey));
extractorWrappers.get(manifestTrackKey), drmInitData, dataSource, currentAbsoluteChunkIndex,
isLastChunk, chunkStartTimeUs, chunkEndTimeUs, evaluation.trigger,
mediaFormats.get(manifestTrackKey), enabledTrack.adaptiveMaxWidth,
enabledTrack.adaptiveMaxHeight);
out.chunk = mediaChunk;
}
@ -480,14 +471,14 @@ public class SmoothStreamingChunkSource implements ChunkSource,
private static MediaChunk newMediaChunk(Format formatInfo, Uri uri, String cacheKey,
ChunkExtractorWrapper extractorWrapper, DrmInitData drmInitData, DataSource dataSource,
int chunkIndex, boolean isLast, long chunkStartTimeUs, long chunkEndTimeUs,
int trigger, MediaFormat mediaFormat) {
int trigger, MediaFormat mediaFormat, int adaptiveMaxWidth, int adaptiveMaxHeight) {
long offset = 0;
DataSpec dataSpec = new DataSpec(uri, offset, -1, cacheKey);
// In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk.
// To convert them the absolute timestamps, we need to set sampleOffsetUs to -chunkStartTimeUs.
return new ContainerMediaChunk(dataSource, dataSpec, trigger, formatInfo, chunkStartTimeUs,
chunkEndTimeUs, chunkIndex, isLast, chunkStartTimeUs, extractorWrapper, mediaFormat,
drmInitData, true, Chunk.NO_PARENT_ID);
adaptiveMaxWidth, adaptiveMaxHeight, drmInitData, true, Chunk.NO_PARENT_ID);
}
private static int getManifestTrackKey(int elementIndex, int trackIndex) {
@ -538,17 +529,17 @@ public class SmoothStreamingChunkSource implements ChunkSource,
this.elementIndex = elementIndex;
this.fixedFormat = fixedFormat;
this.adaptiveFormats = null;
this.adaptiveMaxWidth = -1;
this.adaptiveMaxHeight = -1;
this.adaptiveMaxWidth = MediaFormat.NO_VALUE;
this.adaptiveMaxHeight = MediaFormat.NO_VALUE;
}
public ExposedTrack(MediaFormat format, int elementIndex, Format[] adaptiveFormats,
int maxWidth, int maxHeight) {
int adaptiveMaxWidth, int adaptiveMaxHeight) {
this.format = format;
this.elementIndex = elementIndex;
this.adaptiveFormats = adaptiveFormats;
this.adaptiveMaxWidth = maxWidth;
this.adaptiveMaxHeight = maxHeight;
this.adaptiveMaxWidth = adaptiveMaxWidth;
this.adaptiveMaxHeight = adaptiveMaxHeight;
this.fixedFormat = null;
}