Commit b405d3d9 by Oliver Woodman

Have Representation, TrackElement and Variant consistently expose Format.

And delete things that we're parsing but don't use from TrackElement.
parent 8673cafc
...@@ -218,10 +218,9 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -218,10 +218,9 @@ public class DashRendererBuilder implements RendererBuilder,
// Determine which video representations we should use for playback. // Determine which video representations we should use for playback.
int[] videoRepresentationIndices = null; int[] videoRepresentationIndices = null;
if (videoAdaptationSet != null) { if (videoAdaptationSet != null) {
Format[] formats = getFormats(videoAdaptationSet.representations);
try { try {
videoRepresentationIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay( videoRepresentationIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay(
context, formats, null, filterHdContent); context, videoAdaptationSet.representations, null, filterHdContent);
} catch (DecoderQueryException e) { } catch (DecoderQueryException e) {
callback.onRenderersError(e); callback.onRenderersError(e);
return; return;
...@@ -363,14 +362,6 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -363,14 +362,6 @@ public class DashRendererBuilder implements RendererBuilder,
callback.onRenderers(trackNames, multiTrackChunkSources, renderers); callback.onRenderers(trackNames, multiTrackChunkSources, renderers);
} }
private static Format[] getFormats(List<Representation> representations) {
Format[] formats = new Format[representations.size()];
for (int i = 0; i < formats.length; i++) {
formats[i] = representations.get(i).format;
}
return formats;
}
@TargetApi(18) @TargetApi(18)
private static class V18Compat { private static class V18Compat {
......
...@@ -23,7 +23,6 @@ import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; ...@@ -23,7 +23,6 @@ import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.chunk.ChunkSampleSource; import com.google.android.exoplayer.chunk.ChunkSampleSource;
import com.google.android.exoplayer.chunk.ChunkSource; import com.google.android.exoplayer.chunk.ChunkSource;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.chunk.FormatEvaluator; import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator; import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
import com.google.android.exoplayer.chunk.MultiTrackChunkSource; import com.google.android.exoplayer.chunk.MultiTrackChunkSource;
...@@ -36,7 +35,6 @@ import com.google.android.exoplayer.drm.StreamingDrmSessionManager; ...@@ -36,7 +35,6 @@ import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifestParser; import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifestParser;
import com.google.android.exoplayer.text.TextTrackRenderer; import com.google.android.exoplayer.text.TextTrackRenderer;
import com.google.android.exoplayer.text.ttml.TtmlParser; import com.google.android.exoplayer.text.ttml.TtmlParser;
...@@ -56,6 +54,7 @@ import android.os.Handler; ...@@ -56,6 +54,7 @@ import android.os.Handler;
import android.widget.TextView; import android.widget.TextView;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.UUID; import java.util.UUID;
/** /**
...@@ -145,10 +144,9 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -145,10 +144,9 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
// Determine which video tracks we should use for playback. // Determine which video tracks we should use for playback.
int[] videoTrackIndices = null; int[] videoTrackIndices = null;
if (videoStreamElementIndex != -1) { if (videoStreamElementIndex != -1) {
Format[] formats = getFormats(manifest.streamElements[videoStreamElementIndex].tracks);
try { try {
videoTrackIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay(context, videoTrackIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay(context,
formats, null, false); Arrays.asList(manifest.streamElements[videoStreamElementIndex].tracks), null, false);
} catch (DecoderQueryException e) { } catch (DecoderQueryException e) {
callback.onRenderersError(e); callback.onRenderersError(e);
return; return;
...@@ -254,17 +252,6 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -254,17 +252,6 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
callback.onRenderers(trackNames, multiTrackChunkSources, renderers); callback.onRenderers(trackNames, multiTrackChunkSources, renderers);
} }
private static Format[] getFormats(TrackElement[] trackElements) {
Format[] formats = new Format[trackElements.length];
for (int i = 0; i < formats.length; i++) {
TrackElement trackElement = trackElements[i];
formats[i] = new Format(String.valueOf(i), trackElement.mimeType, trackElement.maxWidth,
trackElement.maxHeight, -1, trackElement.numChannels, trackElement.sampleRate,
trackElement.bitrate);
}
return formats;
}
@TargetApi(18) @TargetApi(18)
private static class V18Compat { private static class V18Compat {
......
/*
* Copyright (C) 2014 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.exoplayer.chunk;
/**
* Represents an object that wraps a {@link Format}.
*/
public interface FormatWrapper {
/**
* Returns the wrapped format.
*/
Format getFormat();
}
...@@ -26,6 +26,7 @@ import android.view.Display; ...@@ -26,6 +26,7 @@ import android.view.Display;
import android.view.WindowManager; import android.view.WindowManager;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
/** /**
* Selects from possible video formats. * Selects from possible video formats.
...@@ -44,19 +45,20 @@ public final class VideoFormatSelectorUtil { ...@@ -44,19 +45,20 @@ public final class VideoFormatSelectorUtil {
* default display. * default display.
* *
* @param context A context. * @param context A context.
* @param formats The formats from which to select. * @param formatWrappers Wrapped formats from which to select.
* @param allowedContainerMimeTypes An array of allowed container mime types. Null allows all * @param allowedContainerMimeTypes An array of allowed container mime types. Null allows all
* mime types. * mime types.
* @param filterHdFormats True to filter HD formats. False otherwise. * @param filterHdFormats True to filter HD formats. False otherwise.
* @return An array holding the indices of the selected formats. * @return An array holding the indices of the selected formats.
* @throws DecoderQueryException * @throws DecoderQueryException
*/ */
public static int[] selectVideoFormatsForDefaultDisplay(Context context, Format[] formats, public static int[] selectVideoFormatsForDefaultDisplay(Context context,
String[] allowedContainerMimeTypes, boolean filterHdFormats) throws DecoderQueryException { List<? extends FormatWrapper> formatWrappers, String[] allowedContainerMimeTypes,
boolean filterHdFormats) throws DecoderQueryException {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay(); Display display = windowManager.getDefaultDisplay();
Point displaySize = getDisplaySize(display); Point displaySize = getDisplaySize(display);
return selectVideoFormats(formats, allowedContainerMimeTypes, filterHdFormats, true, return selectVideoFormats(formatWrappers, allowedContainerMimeTypes, filterHdFormats, true,
displaySize.x, displaySize.y); displaySize.x, displaySize.y);
} }
...@@ -73,7 +75,7 @@ public final class VideoFormatSelectorUtil { ...@@ -73,7 +75,7 @@ public final class VideoFormatSelectorUtil {
* in pixels that the video can be rendered within the viewport. * in pixels that the video can be rendered within the viewport.
* </ul> * </ul>
* *
* @param formats The formats from which to select. * @param formatWrappers Wrapped formats from which to select.
* @param allowedContainerMimeTypes An array of allowed container mime types. Null allows all * @param allowedContainerMimeTypes An array of allowed container mime types. Null allows all
* mime types. * mime types.
* @param filterHdFormats True to filter HD formats. False otherwise. * @param filterHdFormats True to filter HD formats. False otherwise.
...@@ -88,16 +90,17 @@ public final class VideoFormatSelectorUtil { ...@@ -88,16 +90,17 @@ public final class VideoFormatSelectorUtil {
* @return An array holding the indices of the selected formats. * @return An array holding the indices of the selected formats.
* @throws DecoderQueryException * @throws DecoderQueryException
*/ */
public static int[] selectVideoFormats(Format[] formats, String[] allowedContainerMimeTypes, public static int[] selectVideoFormats(List<? extends FormatWrapper> formatWrappers,
boolean filterHdFormats, boolean orientationMayChange, int viewportWidth, int viewportHeight) String[] allowedContainerMimeTypes, boolean filterHdFormats, boolean orientationMayChange,
throws DecoderQueryException { int viewportWidth, int viewportHeight) throws DecoderQueryException {
int maxVideoPixelsToRetain = Integer.MAX_VALUE; int maxVideoPixelsToRetain = Integer.MAX_VALUE;
ArrayList<Integer> selectedIndexList = new ArrayList<Integer>(); ArrayList<Integer> selectedIndexList = new ArrayList<Integer>();
int maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize(); int maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
// First pass to filter out formats that individually fail to meet the selection criteria. // First pass to filter out formats that individually fail to meet the selection criteria.
for (int i = 0; i < formats.length; i++) { int formatWrapperCount = formatWrappers.size();
Format format = formats[i]; for (int i = 0; i < formatWrapperCount; i++) {
Format format = formatWrappers.get(i).getFormat();
if (isFormatPlayable(format, allowedContainerMimeTypes, filterHdFormats, if (isFormatPlayable(format, allowedContainerMimeTypes, filterHdFormats,
maxDecodableFrameSize)) { maxDecodableFrameSize)) {
// Select the format for now. It may still be filtered in the second pass below. // Select the format for now. It may still be filtered in the second pass below.
...@@ -122,7 +125,7 @@ public final class VideoFormatSelectorUtil { ...@@ -122,7 +125,7 @@ public final class VideoFormatSelectorUtil {
// unnecessarily high resolution given the size at which the video will be displayed within the // unnecessarily high resolution given the size at which the video will be displayed within the
// viewport. // viewport.
for (int i = selectedIndexList.size() - 1; i >= 0; i--) { for (int i = selectedIndexList.size() - 1; i >= 0; i--) {
Format format = formats[selectedIndexList.get(i)]; Format format = formatWrappers.get(i).getFormat();
int videoPixels = format.width * format.height; int videoPixels = format.width * format.height;
if (format.width != -1 && format.height != -1 && videoPixels > maxVideoPixelsToRetain) { if (format.width != -1 && format.height != -1 && videoPixels > maxVideoPixelsToRetain) {
selectedIndexList.remove(i); selectedIndexList.remove(i);
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer.dash.mpd; package com.google.android.exoplayer.dash.mpd;
import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.chunk.FormatWrapper;
import com.google.android.exoplayer.dash.DashSegmentIndex; import com.google.android.exoplayer.dash.DashSegmentIndex;
import com.google.android.exoplayer.dash.DashSingleSegmentIndex; import com.google.android.exoplayer.dash.DashSingleSegmentIndex;
import com.google.android.exoplayer.dash.mpd.SegmentBase.MultiSegmentBase; import com.google.android.exoplayer.dash.mpd.SegmentBase.MultiSegmentBase;
...@@ -26,7 +27,7 @@ import android.net.Uri; ...@@ -26,7 +27,7 @@ import android.net.Uri;
/** /**
* A DASH representation. * A DASH representation.
*/ */
public abstract class Representation { public abstract class Representation implements FormatWrapper {
/** /**
* Identifies the piece of content to which this {@link Representation} belongs. * Identifies the piece of content to which this {@link Representation} belongs.
...@@ -105,6 +106,11 @@ public abstract class Representation { ...@@ -105,6 +106,11 @@ public abstract class Representation {
presentationTimeOffsetUs = segmentBase.getPresentationTimeOffsetUs(); presentationTimeOffsetUs = segmentBase.getPresentationTimeOffsetUs();
} }
@Override
public Format getFormat() {
return format;
}
/** /**
* Gets a {@link RangedUri} defining the location of the representation's initialization data. * Gets a {@link RangedUri} defining the location of the representation's initialization data.
* May be null if no initialization data exists. * May be null if no initialization data exists.
......
...@@ -29,7 +29,6 @@ import com.google.android.exoplayer.upstream.DataSource; ...@@ -29,7 +29,6 @@ import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.upstream.HttpDataSource.InvalidResponseCodeException; import com.google.android.exoplayer.upstream.HttpDataSource.InvalidResponseCodeException;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.UriUtil; import com.google.android.exoplayer.util.UriUtil;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
...@@ -118,7 +117,7 @@ public class HlsChunkSource { ...@@ -118,7 +117,7 @@ public class HlsChunkSource {
private final DataSource dataSource; private final DataSource dataSource;
private final HlsPlaylistParser playlistParser; private final HlsPlaylistParser playlistParser;
private final List<Variant> variants; private final List<Variant> variants;
private final HlsFormat[] enabledFormats; private final Format[] enabledFormats;
private final BandwidthMeter bandwidthMeter; private final BandwidthMeter bandwidthMeter;
private final int adaptiveMode; private final int adaptiveMode;
private final String baseUri; private final String baseUri;
...@@ -173,7 +172,7 @@ public class HlsChunkSource { ...@@ -173,7 +172,7 @@ public class HlsChunkSource {
playlistParser = new HlsPlaylistParser(); playlistParser = new HlsPlaylistParser();
if (playlist.type == HlsPlaylist.TYPE_MEDIA) { if (playlist.type == HlsPlaylist.TYPE_MEDIA) {
variants = Collections.singletonList(new Variant(playlistUrl, 0, null, -1, -1)); variants = Collections.singletonList(new Variant(0, playlistUrl, 0, null, -1, -1));
variantIndices = null; variantIndices = null;
mediaPlaylists = new HlsMediaPlaylist[1]; mediaPlaylists = new HlsMediaPlaylist[1];
mediaPlaylistBlacklistTimesMs = new long[1]; mediaPlaylistBlacklistTimesMs = new long[1];
...@@ -194,8 +193,9 @@ public class HlsChunkSource { ...@@ -194,8 +193,9 @@ public class HlsChunkSource {
// Select the first variant from the master playlist that's enabled. // Select the first variant from the master playlist that's enabled.
int minEnabledVariantIndex = Integer.MAX_VALUE; int minEnabledVariantIndex = Integer.MAX_VALUE;
for (int i = 0; i < enabledFormats.length; i++) { for (int i = 0; i < enabledFormats.length; i++) {
if (enabledFormats[i].variantIndex < minEnabledVariantIndex) { int variantIndex = getVariantIndex(enabledFormats[i]);
minEnabledVariantIndex = enabledFormats[i].variantIndex; if (variantIndex < minEnabledVariantIndex) {
minEnabledVariantIndex = variantIndex;
formatIndex = i; formatIndex = i;
} }
maxWidth = Math.max(enabledFormats[i].width, maxWidth); maxWidth = Math.max(enabledFormats[i].width, maxWidth);
...@@ -246,7 +246,7 @@ public class HlsChunkSource { ...@@ -246,7 +246,7 @@ public class HlsChunkSource {
switchingVariantSpliced = switchingVariant && adaptiveMode == ADAPTIVE_MODE_SPLICE; switchingVariantSpliced = switchingVariant && adaptiveMode == ADAPTIVE_MODE_SPLICE;
} }
int variantIndex = enabledFormats[nextFormatIndex].variantIndex; int variantIndex = getVariantIndex(enabledFormats[nextFormatIndex]);
HlsMediaPlaylist mediaPlaylist = mediaPlaylists[variantIndex]; HlsMediaPlaylist mediaPlaylist = mediaPlaylists[variantIndex];
if (mediaPlaylist == null) { if (mediaPlaylist == null) {
// We don't have the media playlist for the next variant. Request it now. // We don't have the media playlist for the next variant. Request it now.
...@@ -432,7 +432,7 @@ public class HlsChunkSource { ...@@ -432,7 +432,7 @@ public class HlsChunkSource {
private int getFormatIndexForBandwidth(int bitrate) { private int getFormatIndexForBandwidth(int bitrate) {
int lowestQualityEnabledFormatIndex = -1; int lowestQualityEnabledFormatIndex = -1;
for (int i = 0; i < enabledFormats.length; i++) { for (int i = 0; i < enabledFormats.length; i++) {
int variantIndex = enabledFormats[i].variantIndex; int variantIndex = getVariantIndex(enabledFormats[i]);
if (mediaPlaylistBlacklistTimesMs[variantIndex] == 0) { if (mediaPlaylistBlacklistTimesMs[variantIndex] == 0) {
if (enabledFormats[i].bitrate <= bitrate) { if (enabledFormats[i].bitrate <= bitrate) {
return i; return i;
...@@ -507,7 +507,7 @@ public class HlsChunkSource { ...@@ -507,7 +507,7 @@ public class HlsChunkSource {
durationUs = mediaPlaylist.durationUs; durationUs = mediaPlaylist.durationUs;
} }
private static HlsFormat[] buildEnabledFormats(List<Variant> variants, int[] variantIndices) { private static Format[] buildEnabledFormats(List<Variant> variants, int[] variantIndices) {
ArrayList<Variant> enabledVariants = new ArrayList<Variant>(); ArrayList<Variant> enabledVariants = new ArrayList<Variant>();
if (variantIndices != null) { if (variantIndices != null) {
for (int i = 0; i < variantIndices.length; i++) { for (int i = 0; i < variantIndices.length; i++) {
...@@ -522,7 +522,7 @@ public class HlsChunkSource { ...@@ -522,7 +522,7 @@ public class HlsChunkSource {
ArrayList<Variant> definiteAudioOnlyVariants = new ArrayList<Variant>(); ArrayList<Variant> definiteAudioOnlyVariants = new ArrayList<Variant>();
for (int i = 0; i < enabledVariants.size(); i++) { for (int i = 0; i < enabledVariants.size(); i++) {
Variant variant = enabledVariants.get(i); Variant variant = enabledVariants.get(i);
if (variant.height > 0 || variantHasExplicitCodecWithPrefix(variant, "avc")) { if (variant.format.height > 0 || variantHasExplicitCodecWithPrefix(variant, "avc")) {
definiteVideoVariants.add(variant); definiteVideoVariants.add(variant);
} else if (variantHasExplicitCodecWithPrefix(variant, "mp4a")) { } else if (variantHasExplicitCodecWithPrefix(variant, "mp4a")) {
definiteAudioOnlyVariants.add(variant); definiteAudioOnlyVariants.add(variant);
...@@ -542,12 +542,9 @@ public class HlsChunkSource { ...@@ -542,12 +542,9 @@ public class HlsChunkSource {
// Leave the enabled variants unchanged. They're likely either all video or all audio. // Leave the enabled variants unchanged. They're likely either all video or all audio.
} }
HlsFormat[] enabledFormats = new HlsFormat[enabledVariants.size()]; Format[] enabledFormats = new Format[enabledVariants.size()];
for (int i = 0; i < enabledFormats.length; i++) { for (int i = 0; i < enabledFormats.length; i++) {
Variant variant = enabledVariants.get(i); enabledFormats[i] = enabledVariants.get(i).format;
int variantIndex = variants.indexOf(variant);
enabledFormats[i] = new HlsFormat(Integer.toString(variantIndex), variant.width,
variant.height, variant.bitrate, variant.codecs, variantIndex);
} }
Arrays.sort(enabledFormats, new Format.DecreasingBandwidthComparator()); Arrays.sort(enabledFormats, new Format.DecreasingBandwidthComparator());
...@@ -555,7 +552,7 @@ public class HlsChunkSource { ...@@ -555,7 +552,7 @@ public class HlsChunkSource {
} }
private static boolean variantHasExplicitCodecWithPrefix(Variant variant, String prefix) { private static boolean variantHasExplicitCodecWithPrefix(Variant variant, String prefix) {
String codecs = variant.codecs; String codecs = variant.format.codecs;
if (TextUtils.isEmpty(codecs)) { if (TextUtils.isEmpty(codecs)) {
return false; return false;
} }
...@@ -587,6 +584,16 @@ public class HlsChunkSource { ...@@ -587,6 +584,16 @@ public class HlsChunkSource {
} }
} }
private int getVariantIndex(Format format) {
for (int i = 0; i < variants.size(); i++) {
if (format == variants.get(i).format) {
return i;
}
}
// Should never happen.
throw new IllegalStateException("Invalid format: " + format);
}
private static class MediaPlaylistChunk extends DataChunk { private static class MediaPlaylistChunk extends DataChunk {
public final int variantIndex; public final int variantIndex;
...@@ -640,16 +647,4 @@ public class HlsChunkSource { ...@@ -640,16 +647,4 @@ public class HlsChunkSource {
} }
private static final class HlsFormat extends Format {
public final int variantIndex;
public HlsFormat(String id, int width, int height, int bitrate, String codecs,
int variantIndex) {
super(id, MimeTypes.APPLICATION_M3U8, width, height, -1, -1, -1, bitrate, null, codecs);
this.variantIndex = variantIndex;
}
}
} }
...@@ -176,7 +176,7 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist> ...@@ -176,7 +176,7 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
} }
expectingStreamInfUrl = true; expectingStreamInfUrl = true;
} else if (!line.startsWith("#") && expectingStreamInfUrl) { } else if (!line.startsWith("#") && expectingStreamInfUrl) {
variants.add(new Variant(line, bitrate, codecs, width, height)); variants.add(new Variant(variants.size(), line, bitrate, codecs, width, height));
bitrate = 0; bitrate = 0;
codecs = null; codecs = null;
width = -1; width = -1;
......
...@@ -15,23 +15,27 @@ ...@@ -15,23 +15,27 @@
*/ */
package com.google.android.exoplayer.hls; package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.chunk.FormatWrapper;
import com.google.android.exoplayer.util.MimeTypes;
/** /**
* Variant stream reference. * Variant stream reference.
*/ */
public final class Variant { public final class Variant implements FormatWrapper {
public final int bitrate;
public final String url; public final String url;
public final String codecs; public final Format format;
public final int width;
public final int height;
public Variant(String url, int bitrate, String codecs, int width, int height) { public Variant(int index, String url, int bitrate, String codecs, int width, int height) {
this.bitrate = bitrate;
this.url = url; this.url = url;
this.codecs = codecs; format = new Format(Integer.toString(index), MimeTypes.APPLICATION_M3U8, width, height, -1, -1,
this.width = width; -1, bitrate, null, codecs);
this.height = height; }
@Override
public Format getFormat() {
return format;
} }
} }
...@@ -73,7 +73,7 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -73,7 +73,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
private final SparseArray<ChunkExtractorWrapper> extractorWrappers; private final SparseArray<ChunkExtractorWrapper> extractorWrappers;
private final SparseArray<MediaFormat> mediaFormats; private final SparseArray<MediaFormat> mediaFormats;
private final DrmInitData drmInitData; private final DrmInitData drmInitData;
private final SmoothStreamingFormat[] formats; private final Format[] formats;
private SmoothStreamingManifest currentManifest; private SmoothStreamingManifest currentManifest;
private int currentManifestChunkOffset; private int currentManifestChunkOffset;
...@@ -135,7 +135,7 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -135,7 +135,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
this.liveEdgeLatencyUs = liveEdgeLatencyMs * 1000; this.liveEdgeLatencyUs = liveEdgeLatencyMs * 1000;
StreamElement streamElement = getElement(initialManifest); StreamElement streamElement = getElement(initialManifest);
trackInfo = new TrackInfo(streamElement.tracks[0].mimeType, initialManifest.durationUs); trackInfo = new TrackInfo(streamElement.tracks[0].format.mimeType, initialManifest.durationUs);
evaluation = new Evaluation(); evaluation = new Evaluation();
TrackEncryptionBox[] trackEncryptionBoxes = null; TrackEncryptionBox[] trackEncryptionBoxes = null;
...@@ -152,19 +152,16 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -152,19 +152,16 @@ public class SmoothStreamingChunkSource implements ChunkSource {
} }
int trackCount = trackIndices != null ? trackIndices.length : streamElement.tracks.length; int trackCount = trackIndices != null ? trackIndices.length : streamElement.tracks.length;
formats = new SmoothStreamingFormat[trackCount]; formats = new Format[trackCount];
extractorWrappers = new SparseArray<ChunkExtractorWrapper>(); extractorWrappers = new SparseArray<ChunkExtractorWrapper>();
mediaFormats = new SparseArray<MediaFormat>(); mediaFormats = new SparseArray<MediaFormat>();
int maxWidth = 0; int maxWidth = 0;
int maxHeight = 0; int maxHeight = 0;
for (int i = 0; i < trackCount; i++) { for (int i = 0; i < trackCount; i++) {
int trackIndex = trackIndices != null ? trackIndices[i] : i; int trackIndex = trackIndices != null ? trackIndices[i] : i;
TrackElement trackElement = streamElement.tracks[trackIndex]; formats[i] = streamElement.tracks[trackIndex].format;
formats[i] = new SmoothStreamingFormat(String.valueOf(trackIndex), trackElement.mimeType, maxWidth = Math.max(maxWidth, formats[i].width);
trackElement.maxWidth, trackElement.maxHeight, trackElement.numChannels, maxHeight = Math.max(maxHeight, formats[i].height);
trackElement.sampleRate, trackElement.bitrate, trackIndex);
maxWidth = Math.max(maxWidth, trackElement.maxWidth);
maxHeight = Math.max(maxHeight, trackElement.maxHeight);
MediaFormat mediaFormat = getMediaFormat(streamElement, trackIndex); MediaFormat mediaFormat = getMediaFormat(streamElement, trackIndex);
int trackType = streamElement.type == StreamElement.TYPE_VIDEO ? Track.TYPE_VIDEO int trackType = streamElement.type == StreamElement.TYPE_VIDEO ? Track.TYPE_VIDEO
...@@ -244,7 +241,7 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -244,7 +241,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
evaluation.queueSize = queue.size(); evaluation.queueSize = queue.size();
formatEvaluator.evaluate(queue, playbackPositionUs, formats, evaluation); formatEvaluator.evaluate(queue, playbackPositionUs, formats, evaluation);
SmoothStreamingFormat selectedFormat = (SmoothStreamingFormat) evaluation.format; Format selectedFormat = evaluation.format;
out.queueSize = evaluation.queueSize; out.queueSize = evaluation.queueSize;
if (selectedFormat == null) { if (selectedFormat == null) {
...@@ -304,7 +301,7 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -304,7 +301,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
: chunkStartTimeUs + streamElement.getChunkDurationUs(chunkIndex); : chunkStartTimeUs + streamElement.getChunkDurationUs(chunkIndex);
int currentAbsoluteChunkIndex = chunkIndex + currentManifestChunkOffset; int currentAbsoluteChunkIndex = chunkIndex + currentManifestChunkOffset;
int trackIndex = selectedFormat.trackIndex; int trackIndex = getTrackIndex(selectedFormat);
Uri uri = streamElement.buildRequestUri(trackIndex, chunkIndex); Uri uri = streamElement.buildRequestUri(trackIndex, chunkIndex);
Chunk mediaChunk = newMediaChunk(selectedFormat, uri, null, extractorWrappers.get(trackIndex), Chunk mediaChunk = newMediaChunk(selectedFormat, uri, null, extractorWrappers.get(trackIndex),
drmInitData, dataSource, currentAbsoluteChunkIndex, isLastChunk, chunkStartTimeUs, drmInitData, dataSource, currentAbsoluteChunkIndex, isLastChunk, chunkStartTimeUs,
...@@ -352,12 +349,24 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -352,12 +349,24 @@ public class SmoothStreamingChunkSource implements ChunkSource {
return manifest.streamElements[streamElementIndex]; return manifest.streamElements[streamElementIndex];
} }
private int getTrackIndex(Format format) {
TrackElement[] tracks = currentManifest.streamElements[streamElementIndex].tracks;
for (int i = 0; i < tracks.length; i++) {
if (format == tracks[i].format) {
return i;
}
}
// Should never happen.
throw new IllegalStateException("Invalid format: " + format);
}
private static MediaFormat getMediaFormat(StreamElement streamElement, int trackIndex) { private static MediaFormat getMediaFormat(StreamElement streamElement, int trackIndex) {
TrackElement trackElement = streamElement.tracks[trackIndex]; TrackElement trackElement = streamElement.tracks[trackIndex];
String mimeType = trackElement.mimeType; Format trackFormat = trackElement.format;
String mimeType = trackFormat.mimeType;
if (streamElement.type == StreamElement.TYPE_VIDEO) { if (streamElement.type == StreamElement.TYPE_VIDEO) {
MediaFormat format = MediaFormat.createVideoFormat(mimeType, MediaFormat.NO_VALUE, MediaFormat format = MediaFormat.createVideoFormat(mimeType, MediaFormat.NO_VALUE,
trackElement.maxWidth, trackElement.maxHeight, Arrays.asList(trackElement.csd)); trackFormat.width, trackFormat.height, Arrays.asList(trackElement.csd));
format.setMaxVideoDimensions(streamElement.maxWidth, streamElement.maxHeight); format.setMaxVideoDimensions(streamElement.maxWidth, streamElement.maxHeight);
return format; return format;
} else if (streamElement.type == StreamElement.TYPE_AUDIO) { } else if (streamElement.type == StreamElement.TYPE_AUDIO) {
...@@ -366,13 +375,13 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -366,13 +375,13 @@ public class SmoothStreamingChunkSource implements ChunkSource {
csd = Arrays.asList(trackElement.csd); csd = Arrays.asList(trackElement.csd);
} else { } else {
csd = Collections.singletonList(CodecSpecificDataUtil.buildAudioSpecificConfig( csd = Collections.singletonList(CodecSpecificDataUtil.buildAudioSpecificConfig(
trackElement.sampleRate, trackElement.numChannels)); trackFormat.audioSamplingRate, trackFormat.numChannels));
} }
MediaFormat format = MediaFormat.createAudioFormat(mimeType, MediaFormat.NO_VALUE, MediaFormat format = MediaFormat.createAudioFormat(mimeType, MediaFormat.NO_VALUE,
trackElement.numChannels, trackElement.sampleRate, csd); trackFormat.numChannels, trackFormat.audioSamplingRate, csd);
return format; return format;
} else if (streamElement.type == StreamElement.TYPE_TEXT) { } else if (streamElement.type == StreamElement.TYPE_TEXT) {
return MediaFormat.createFormatForMimeType(streamElement.tracks[trackIndex].mimeType); return MediaFormat.createFormatForMimeType(trackFormat.mimeType);
} }
return null; return null;
} }
...@@ -412,16 +421,4 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -412,16 +421,4 @@ public class SmoothStreamingChunkSource implements ChunkSource {
data[secondPosition] = temp; data[secondPosition] = temp;
} }
private static final class SmoothStreamingFormat extends Format {
public final int trackIndex;
public SmoothStreamingFormat(String id, String mimeType, int width, int height,
int numChannels, int audioSamplingRate, int bitrate, int trackIndex) {
super(id, mimeType, width, height, -1, numChannels, audioSamplingRate, bitrate);
this.trackIndex = trackIndex;
}
}
} }
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package com.google.android.exoplayer.smoothstreaming; package com.google.android.exoplayer.smoothstreaming;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.chunk.FormatWrapper;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.UriUtil; import com.google.android.exoplayer.util.UriUtil;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
...@@ -124,50 +126,21 @@ public class SmoothStreamingManifest { ...@@ -124,50 +126,21 @@ public class SmoothStreamingManifest {
/** /**
* Represents a QualityLevel element. * Represents a QualityLevel element.
*/ */
public static class TrackElement { public static class TrackElement implements FormatWrapper {
// Required for all public final Format format;
public final int index;
public final int bitrate;
// Audio-video
public final byte[][] csd; public final byte[][] csd;
public final int profile;
public final int level;
public final String mimeType;
// Video-only
public final int maxWidth;
public final int maxHeight;
// Audio-only public TrackElement(int index, int bitrate, String mimeType, byte[][] csd, int maxWidth,
public final int sampleRate; int maxHeight, int sampleRate, int numChannels) {
public final int numChannels;
public final int packetSize;
public final int audioTag;
public final int bitPerSample;
public final int nalUnitLengthField;
public final String content;
public TrackElement(int index, int bitrate, String mimeType, byte[][] csd, int profile,
int level, int maxWidth, int maxHeight, int sampleRate, int channels, int packetSize,
int audioTag, int bitPerSample, int nalUnitLengthField, String content) {
this.index = index;
this.bitrate = bitrate;
this.mimeType = mimeType;
this.csd = csd; this.csd = csd;
this.profile = profile; format = new Format(String.valueOf(index), mimeType, maxWidth, maxHeight, -1, numChannels,
this.level = level; sampleRate, bitrate);
this.maxWidth = maxWidth; }
this.maxHeight = maxHeight;
this.sampleRate = sampleRate; @Override
this.numChannels = channels; public Format getFormat() {
this.packetSize = packetSize; return format;
this.audioTag = audioTag;
this.bitPerSample = bitPerSample;
this.nalUnitLengthField = nalUnitLengthField;
this.content = content;
} }
} }
...@@ -273,7 +246,7 @@ public class SmoothStreamingManifest { ...@@ -273,7 +246,7 @@ public class SmoothStreamingManifest {
Assertions.checkState(chunkStartTimes != null); Assertions.checkState(chunkStartTimes != null);
Assertions.checkState(chunkIndex < chunkStartTimes.size()); Assertions.checkState(chunkIndex < chunkStartTimes.size());
String chunkUrl = chunkTemplate String chunkUrl = chunkTemplate
.replace(URL_PLACEHOLDER_BITRATE, Integer.toString(tracks[track].bitrate)) .replace(URL_PLACEHOLDER_BITRATE, Integer.toString(tracks[track].format.bitrate))
.replace(URL_PLACEHOLDER_START_TIME, chunkStartTimes.get(chunkIndex).toString()); .replace(URL_PLACEHOLDER_START_TIME, chunkStartTimes.get(chunkIndex).toString());
return UriUtil.resolveToUri(baseUri, chunkUrl); return UriUtil.resolveToUri(baseUri, chunkUrl);
} }
......
...@@ -585,11 +585,7 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS ...@@ -585,11 +585,7 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS
private static final String KEY_CODEC_PRIVATE_DATA = "CodecPrivateData"; private static final String KEY_CODEC_PRIVATE_DATA = "CodecPrivateData";
private static final String KEY_SAMPLING_RATE = "SamplingRate"; private static final String KEY_SAMPLING_RATE = "SamplingRate";
private static final String KEY_CHANNELS = "Channels"; private static final String KEY_CHANNELS = "Channels";
private static final String KEY_BITS_PER_SAMPLE = "BitsPerSample";
private static final String KEY_PACKET_SIZE = "PacketSize";
private static final String KEY_AUDIO_TAG = "AudioTag";
private static final String KEY_FOUR_CC = "FourCC"; private static final String KEY_FOUR_CC = "FourCC";
private static final String KEY_NAL_UNIT_LENGTH_FIELD = "NALUnitLengthField";
private static final String KEY_TYPE = "Type"; private static final String KEY_TYPE = "Type";
private static final String KEY_MAX_WIDTH = "MaxWidth"; private static final String KEY_MAX_WIDTH = "MaxWidth";
private static final String KEY_MAX_HEIGHT = "MaxHeight"; private static final String KEY_MAX_HEIGHT = "MaxHeight";
...@@ -599,18 +595,10 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS ...@@ -599,18 +595,10 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS
private int index; private int index;
private int bitrate; private int bitrate;
private String mimeType; private String mimeType;
private int profile;
private int level;
private int maxWidth; private int maxWidth;
private int maxHeight; private int maxHeight;
private int samplingRate; private int samplingRate;
private int channels; private int channels;
private int packetSize;
private int audioTag;
private int bitPerSample;
private int nalUnitLengthField;
private String content;
public TrackElementParser(ElementParser parent, String baseUri) { public TrackElementParser(ElementParser parent, String baseUri) {
super(parent, baseUri, TAG); super(parent, baseUri, TAG);
...@@ -620,12 +608,10 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS ...@@ -620,12 +608,10 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS
@Override @Override
public void parseStartTag(XmlPullParser parser) throws ParserException { public void parseStartTag(XmlPullParser parser) throws ParserException {
int type = (Integer) getNormalizedAttribute(KEY_TYPE); int type = (Integer) getNormalizedAttribute(KEY_TYPE);
content = null;
String value; String value;
index = parseInt(parser, KEY_INDEX, -1); index = parseInt(parser, KEY_INDEX, -1);
bitrate = parseRequiredInt(parser, KEY_BITRATE); bitrate = parseRequiredInt(parser, KEY_BITRATE);
nalUnitLengthField = parseInt(parser, KEY_NAL_UNIT_LENGTH_FIELD, 4);
if (type == StreamElement.TYPE_VIDEO) { if (type == StreamElement.TYPE_VIDEO) {
maxHeight = parseRequiredInt(parser, KEY_MAX_HEIGHT); maxHeight = parseRequiredInt(parser, KEY_MAX_HEIGHT);
...@@ -643,15 +629,9 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS ...@@ -643,15 +629,9 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS
if (type == StreamElement.TYPE_AUDIO) { if (type == StreamElement.TYPE_AUDIO) {
samplingRate = parseRequiredInt(parser, KEY_SAMPLING_RATE); samplingRate = parseRequiredInt(parser, KEY_SAMPLING_RATE);
channels = parseRequiredInt(parser, KEY_CHANNELS); channels = parseRequiredInt(parser, KEY_CHANNELS);
bitPerSample = parseRequiredInt(parser, KEY_BITS_PER_SAMPLE);
packetSize = parseRequiredInt(parser, KEY_PACKET_SIZE);
audioTag = parseRequiredInt(parser, KEY_AUDIO_TAG);
} else { } else {
samplingRate = -1; samplingRate = -1;
channels = -1; channels = -1;
bitPerSample = -1;
packetSize = -1;
audioTag = -1;
} }
value = parser.getAttributeValue(null, KEY_CODEC_PRIVATE_DATA); value = parser.getAttributeValue(null, KEY_CODEC_PRIVATE_DATA);
...@@ -662,11 +642,6 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS ...@@ -662,11 +642,6 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS
csd.add(codecPrivateData); csd.add(codecPrivateData);
} else { } else {
for (int i = 0; i < split.length; i++) { for (int i = 0; i < split.length; i++) {
Pair<Integer, Integer> spsParameters = CodecSpecificDataUtil.parseSpsNalUnit(split[i]);
if (spsParameters != null) {
profile = spsParameters.first;
level = spsParameters.second;
}
csd.add(split[i]); csd.add(split[i]);
} }
} }
...@@ -674,20 +649,14 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS ...@@ -674,20 +649,14 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS
} }
@Override @Override
public void parseText(XmlPullParser parser) {
content = parser.getText();
}
@Override
public Object build() { public Object build() {
byte[][] csdArray = null; byte[][] csdArray = null;
if (!csd.isEmpty()) { if (!csd.isEmpty()) {
csdArray = new byte[csd.size()][]; csdArray = new byte[csd.size()][];
csd.toArray(csdArray); csd.toArray(csdArray);
} }
return new TrackElement(index, bitrate, mimeType, csdArray, profile, level, maxWidth, return new TrackElement(index, bitrate, mimeType, csdArray, maxWidth, maxHeight, samplingRate,
maxHeight, samplingRate, channels, packetSize, audioTag, bitPerSample, nalUnitLengthField, channels);
content);
} }
private static String fourCCToMimeType(String fourCC) { private static String fourCCToMimeType(String fourCC) {
......
...@@ -60,35 +60,35 @@ public class HlsMasterPlaylistParserTest extends TestCase { ...@@ -60,35 +60,35 @@ public class HlsMasterPlaylistParserTest extends TestCase {
assertNotNull(variants); assertNotNull(variants);
assertEquals(5, variants.size()); assertEquals(5, variants.size());
assertEquals(1280000, variants.get(0).bitrate); assertEquals(1280000, variants.get(0).format.bitrate);
assertNotNull(variants.get(0).codecs); assertNotNull(variants.get(0).format.codecs);
assertEquals("mp4a.40.2,avc1.66.30", variants.get(0).codecs); assertEquals("mp4a.40.2,avc1.66.30", variants.get(0).format.codecs);
assertEquals(304, variants.get(0).width); assertEquals(304, variants.get(0).format.width);
assertEquals(128, variants.get(0).height); assertEquals(128, variants.get(0).format.height);
assertEquals("http://example.com/low.m3u8", variants.get(0).url); assertEquals("http://example.com/low.m3u8", variants.get(0).url);
assertEquals(1280000, variants.get(1).bitrate); assertEquals(1280000, variants.get(1).format.bitrate);
assertNotNull(variants.get(1).codecs); assertNotNull(variants.get(1).format.codecs);
assertEquals("mp4a.40.2 , avc1.66.30 ", variants.get(1).codecs); assertEquals("mp4a.40.2 , avc1.66.30 ", variants.get(1).format.codecs);
assertEquals("http://example.com/spaces_in_codecs.m3u8", variants.get(1).url); assertEquals("http://example.com/spaces_in_codecs.m3u8", variants.get(1).url);
assertEquals(2560000, variants.get(2).bitrate); assertEquals(2560000, variants.get(2).format.bitrate);
assertEquals(null, variants.get(2).codecs); assertEquals(null, variants.get(2).format.codecs);
assertEquals(384, variants.get(2).width); assertEquals(384, variants.get(2).format.width);
assertEquals(160, variants.get(2).height); assertEquals(160, variants.get(2).format.height);
assertEquals("http://example.com/mid.m3u8", variants.get(2).url); assertEquals("http://example.com/mid.m3u8", variants.get(2).url);
assertEquals(7680000, variants.get(3).bitrate); assertEquals(7680000, variants.get(3).format.bitrate);
assertEquals(null, variants.get(3).codecs); assertEquals(null, variants.get(3).format.codecs);
assertEquals(-1, variants.get(3).width); assertEquals(-1, variants.get(3).format.width);
assertEquals(-1, variants.get(3).height); assertEquals(-1, variants.get(3).format.height);
assertEquals("http://example.com/hi.m3u8", variants.get(3).url); assertEquals("http://example.com/hi.m3u8", variants.get(3).url);
assertEquals(65000, variants.get(4).bitrate); assertEquals(65000, variants.get(4).format.bitrate);
assertNotNull(variants.get(4).codecs); assertNotNull(variants.get(4).format.codecs);
assertEquals("mp4a.40.5", variants.get(4).codecs); assertEquals("mp4a.40.5", variants.get(4).format.codecs);
assertEquals(-1, variants.get(4).width); assertEquals(-1, variants.get(4).format.width);
assertEquals(-1, variants.get(4).height); assertEquals(-1, variants.get(4).format.height);
assertEquals("http://example.com/audio-only.m3u8", variants.get(4).url); assertEquals("http://example.com/audio-only.m3u8", variants.get(4).url);
} catch (IOException exception) { } catch (IOException exception) {
fail(exception.getMessage()); fail(exception.getMessage());
......
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