Commit cb85dc25 by Oliver Woodman

Clean up max dimension handling.

parent 4a9ff7b0
......@@ -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;
......
......@@ -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()) {
......
......@@ -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;
......
......@@ -77,19 +77,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>
* This method should only be called when the source is enabled.
......
......@@ -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;
}
......
......@@ -109,11 +109,6 @@ public final class MultiTrackChunkSource implements ChunkSource, ExoPlayerCompon
}
@Override
public MediaFormat getWithMaxVideoDimensions(MediaFormat format) {
return selectedSource.getWithMaxVideoDimensions(format);
}
@Override
public void handleMessage(int what, Object msg) throws ExoPlaybackException {
Assertions.checkState(!enabled);
if (what == MSG_SELECT_TRACK) {
......
......@@ -70,11 +70,6 @@ public final class SingleSampleChunkSource implements ChunkSource {
}
@Override
public MediaFormat getWithMaxVideoDimensions(MediaFormat format) {
return format;
}
@Override
public void enable(int track) {
// Do nothing.
}
......
......@@ -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;
......
......@@ -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;
}
}
}
......@@ -224,20 +224,6 @@ public class HlsChunkSource {
}
/**
* 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.
*
* @param previousTsChunk The previously loaded chunk that the next chunk should follow.
......@@ -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;
}
......
......@@ -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];
}
/**
......
......@@ -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;
......
......@@ -197,15 +197,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) {
return;
......@@ -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;
}
......
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