Start to fix media mime types.

- Admit we don't know the mime type (using unknown mime types) rather
  than passing the container mime type.
- Pass the correct mime type for opus, vp9 and vp8, and remove the incorrect
  container checks in the corresponding extensions.
This commit is contained in:
Oliver Woodman 2015-09-07 13:56:54 +01:00
parent 4104a8def9
commit 8c3f93d6bf
5 changed files with 105 additions and 57 deletions

View File

@ -38,7 +38,8 @@ import com.google.android.exoplayer.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer.upstream.DefaultUriDataSource; import com.google.android.exoplayer.upstream.DefaultUriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback; import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
import com.google.android.exoplayer.util.MimeTypes;
import android.text.TextUtils;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -82,6 +83,7 @@ public class DashRendererBuilder implements ManifestCallback<MediaPresentationDe
// Obtain Representations for playback. // Obtain Representations for playback.
Representation audioRepresentation = null; Representation audioRepresentation = null;
boolean audioRepresentationIsOpus = false;
ArrayList<Representation> videoRepresentationsList = new ArrayList<>(); ArrayList<Representation> videoRepresentationsList = new ArrayList<>();
Period period = manifest.getPeriod(0); Period period = manifest.getPeriod(0);
for (int i = 0; i < period.adaptationSets.size(); i++) { for (int i = 0; i < period.adaptationSets.size(); i++) {
@ -89,9 +91,12 @@ public class DashRendererBuilder implements ManifestCallback<MediaPresentationDe
int adaptationSetType = adaptationSet.type; int adaptationSetType = adaptationSet.type;
for (int j = 0; j < adaptationSet.representations.size(); j++) { for (int j = 0; j < adaptationSet.representations.size(); j++) {
Representation representation = adaptationSet.representations.get(j); Representation representation = adaptationSet.representations.get(j);
String codecs = representation.format.codecs;
if (adaptationSetType == AdaptationSet.TYPE_AUDIO && audioRepresentation == null) { if (adaptationSetType == AdaptationSet.TYPE_AUDIO && audioRepresentation == null) {
audioRepresentation = representation; audioRepresentation = representation;
} else if (adaptationSetType == AdaptationSet.TYPE_VIDEO) { audioRepresentationIsOpus = !TextUtils.isEmpty(codecs) && codecs.startsWith("opus");
} else if (adaptationSetType == AdaptationSet.TYPE_VIDEO && !TextUtils.isEmpty(codecs)
&& codecs.startsWith("vp9")) {
videoRepresentationsList.add(representation); videoRepresentationsList.add(representation);
} }
} }
@ -103,15 +108,9 @@ public class DashRendererBuilder implements ManifestCallback<MediaPresentationDe
LibvpxVideoTrackRenderer videoRenderer = null; LibvpxVideoTrackRenderer videoRenderer = null;
if (!videoRepresentationsList.isEmpty()) { if (!videoRepresentationsList.isEmpty()) {
DataSource videoDataSource = new DefaultUriDataSource(player, bandwidthMeter, userAgent); DataSource videoDataSource = new DefaultUriDataSource(player, bandwidthMeter, userAgent);
ChunkSource videoChunkSource; ChunkSource videoChunkSource = new DashChunkSource(videoDataSource,
String mimeType = videoRepresentations[0].format.mimeType; new AdaptiveEvaluator(bandwidthMeter), manifest.getPeriodDuration(0),
if (mimeType.equals(MimeTypes.VIDEO_WEBM)) { AdaptationSet.TYPE_VIDEO, videoRepresentations);
videoChunkSource = new DashChunkSource(videoDataSource,
new AdaptiveEvaluator(bandwidthMeter), manifest.getPeriodDuration(0),
AdaptationSet.TYPE_VIDEO, videoRepresentations);
} else {
throw new IllegalStateException("Unexpected mime type: " + mimeType);
}
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE); VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE);
videoRenderer = new LibvpxVideoTrackRenderer(videoSampleSource, videoRenderer = new LibvpxVideoTrackRenderer(videoSampleSource,
@ -128,7 +127,7 @@ public class DashRendererBuilder implements ManifestCallback<MediaPresentationDe
manifest.getPeriodDuration(0), AdaptationSet.TYPE_AUDIO, audioRepresentation); manifest.getPeriodDuration(0), AdaptationSet.TYPE_AUDIO, audioRepresentation);
SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl, SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE); AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE);
if ("opus".equals(audioRepresentation.format.codecs)) { if (audioRepresentationIsOpus) {
audioRenderer = new LibopusAudioTrackRenderer(audioSampleSource); audioRenderer = new LibopusAudioTrackRenderer(audioSampleSource);
} else { } else {
audioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource); audioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource);

View File

@ -126,9 +126,7 @@ public final class LibopusAudioTrackRenderer extends SampleSourceTrackRenderer
@Override @Override
protected boolean handlesTrack(MediaFormat mediaFormat) { protected boolean handlesTrack(MediaFormat mediaFormat) {
// TODO: Stop claiming to handle the WebM mime type (b/22996976). return MimeTypes.AUDIO_OPUS.equalsIgnoreCase(mediaFormat.mimeType);
return MimeTypes.AUDIO_OPUS.equalsIgnoreCase(mediaFormat.mimeType)
|| MimeTypes.AUDIO_WEBM.equalsIgnoreCase(mediaFormat.mimeType);
} }
@Override @Override

View File

@ -152,9 +152,7 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer {
@Override @Override
protected boolean handlesTrack(MediaFormat mediaFormat) { protected boolean handlesTrack(MediaFormat mediaFormat) {
// TODO: Stop claiming to handle the WebM mime type (b/22996976). return MimeTypes.VIDEO_VP9.equalsIgnoreCase(mediaFormat.mimeType);
return MimeTypes.VIDEO_VP9.equalsIgnoreCase(mediaFormat.mimeType)
|| MimeTypes.VIDEO_WEBM.equalsIgnoreCase(mediaFormat.mimeType);
} }
@Override @Override

View File

@ -52,6 +52,8 @@ import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.SystemClock; import com.google.android.exoplayer.util.SystemClock;
import android.os.Handler; import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
import java.io.IOException; import java.io.IOException;
@ -101,6 +103,8 @@ public class DashChunkSource implements ChunkSource, Output {
} }
private static final String TAG = "DashChunkSource";
private final Handler eventHandler; private final Handler eventHandler;
private final EventListener eventListener; private final EventListener eventListener;
@ -138,8 +142,8 @@ public class DashChunkSource implements ChunkSource, Output {
*/ */
public DashChunkSource(DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator, public DashChunkSource(DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
long durationMs, int adaptationSetType, Representation... representations) { long durationMs, int adaptationSetType, Representation... representations) {
this(buildManifest(durationMs, adaptationSetType, Arrays.asList(representations)), null, this(dataSource, adaptiveFormatEvaluator, durationMs, adaptationSetType,
dataSource, adaptiveFormatEvaluator); Arrays.asList(representations));
} }
/** /**
@ -522,7 +526,7 @@ public class DashChunkSource implements ChunkSource, Output {
public void adaptiveTrack(MediaPresentationDescription manifest, int periodIndex, public void adaptiveTrack(MediaPresentationDescription manifest, int periodIndex,
int adaptationSetIndex, int[] representationIndices) { int adaptationSetIndex, int[] representationIndices) {
if (adaptiveFormatEvaluator == null) { if (adaptiveFormatEvaluator == null) {
// Do nothing. Log.w(TAG, "Skipping adaptive track (missing format evaluator)");
return; return;
} }
AdaptationSet adaptationSet = manifest.getPeriod(periodIndex).adaptationSets.get( AdaptationSet adaptationSet = manifest.getPeriod(periodIndex).adaptationSets.get(
@ -542,10 +546,19 @@ public class DashChunkSource implements ChunkSource, Output {
} }
Arrays.sort(representationFormats, new DecreasingBandwidthComparator()); Arrays.sort(representationFormats, new DecreasingBandwidthComparator());
long trackDurationUs = manifest.dynamic ? C.UNKNOWN_TIME_US : manifest.duration * 1000; long trackDurationUs = manifest.dynamic ? C.UNKNOWN_TIME_US : manifest.duration * 1000;
MediaFormat trackFormat = buildTrackFormat(adaptationSet.type, maxHeightRepresentationFormat, String mediaMimeType = getMediaMimeType(maxHeightRepresentationFormat);
trackDurationUs).copyAsAdaptive(); if (mediaMimeType == null) {
tracks.add(new ExposedTrack(trackFormat, adaptationSetIndex, representationFormats, maxWidth, Log.w(TAG, "Skipped adaptive track (unknown media mime type)");
maxHeight)); return;
}
MediaFormat trackFormat = getTrackFormat(adaptationSet.type, maxHeightRepresentationFormat,
mediaMimeType, trackDurationUs);
if (trackFormat == null) {
Log.w(TAG, "Skipped adaptive track (unknown media format)");
return;
}
tracks.add(new ExposedTrack(trackFormat.copyAsAdaptive(), adaptationSetIndex,
representationFormats, maxWidth, maxHeight));
} }
@Override @Override
@ -554,8 +567,17 @@ public class DashChunkSource implements ChunkSource, Output {
List<AdaptationSet> adaptationSets = manifest.getPeriod(periodIndex).adaptationSets; List<AdaptationSet> adaptationSets = manifest.getPeriod(periodIndex).adaptationSets;
AdaptationSet adaptationSet = adaptationSets.get(adaptationSetIndex); AdaptationSet adaptationSet = adaptationSets.get(adaptationSetIndex);
Format representationFormat = adaptationSet.representations.get(representationIndex).format; Format representationFormat = adaptationSet.representations.get(representationIndex).format;
MediaFormat trackFormat = buildTrackFormat(adaptationSet.type, representationFormat, String mediaMimeType = getMediaMimeType(representationFormat);
manifest.dynamic ? C.UNKNOWN_TIME_US : manifest.duration * 1000); if (mediaMimeType == null) {
Log.w(TAG, "Skipped track " + representationFormat.id + " (unknown media mime type)");
return;
}
MediaFormat trackFormat = getTrackFormat(adaptationSet.type, representationFormat,
mediaMimeType, manifest.dynamic ? C.UNKNOWN_TIME_US : manifest.duration * 1000);
if (trackFormat == null) {
Log.w(TAG, "Skipped track " + representationFormat.id + " (unknown media format)");
return;
}
tracks.add(new ExposedTrack(trackFormat, adaptationSetIndex, representationFormat)); tracks.add(new ExposedTrack(trackFormat, adaptationSetIndex, representationFormat));
} }
@ -574,34 +596,64 @@ public class DashChunkSource implements ChunkSource, Output {
Collections.singletonList(period)); Collections.singletonList(period));
} }
private static MediaFormat buildTrackFormat(int adaptationSetType, Format format, private static MediaFormat getTrackFormat(int adaptationSetType, Format format,
long durationUs) { String mediaMimeType, long durationUs) {
switch (adaptationSetType) { switch (adaptationSetType) {
case AdaptationSet.TYPE_VIDEO: case AdaptationSet.TYPE_VIDEO:
return MediaFormat.createVideoFormat(getMediaMimeType(format), format.bitrate, return MediaFormat.createVideoFormat(mediaMimeType, format.bitrate, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE, durationUs, format.width, format.height, 0, null); durationUs, format.width, format.height, 0, null);
case AdaptationSet.TYPE_AUDIO: case AdaptationSet.TYPE_AUDIO:
return MediaFormat.createAudioFormat(getMediaMimeType(format), format.bitrate, return MediaFormat.createAudioFormat(mediaMimeType, format.bitrate, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE, durationUs, format.audioChannels, format.audioSamplingRate, null); durationUs, format.audioChannels, format.audioSamplingRate, null);
case AdaptationSet.TYPE_TEXT: case AdaptationSet.TYPE_TEXT:
return MediaFormat.createTextFormat(getMediaMimeType(format), format.bitrate, return MediaFormat.createTextFormat(mediaMimeType, format.bitrate, format.language,
format.language, durationUs); durationUs);
default: default:
throw new IllegalStateException("Invalid type: " + adaptationSetType); return null;
} }
} }
private static String getMediaMimeType(Format format) { private static String getMediaMimeType(Format format) {
String mimeType = format.mimeType; String formatMimeType = format.mimeType;
if (MimeTypes.APPLICATION_MP4.equals(format.mimeType) && "stpp".equals(format.codecs)) { String codecs = format.codecs;
if (MimeTypes.isAudio(formatMimeType)) {
return getAudioMediaMimeType(codecs);
} else if (MimeTypes.isVideo(formatMimeType)) {
return getVideoMediaMimeType(codecs);
} else if (mimeTypeIsRawText(formatMimeType)) {
return formatMimeType;
} else if (MimeTypes.APPLICATION_MP4.equals(formatMimeType) && "stpp".equals(codecs)) {
return MimeTypes.APPLICATION_TTML; return MimeTypes.APPLICATION_TTML;
} else {
return null;
} }
// TODO: Use codecs to determine media mime type for other formats too. }
return mimeType;
private static String getVideoMediaMimeType(String codecs) {
if (TextUtils.isEmpty(codecs)) {
return MimeTypes.VIDEO_UNKNOWN;
} else if (codecs.startsWith("vp9")) {
return MimeTypes.VIDEO_VP9;
} else if (codecs.startsWith("vp8")) {
return MimeTypes.VIDEO_VP8;
}
// TODO: Parse more codecs values here.
return MimeTypes.VIDEO_UNKNOWN;
}
private static String getAudioMediaMimeType(String codecs) {
if (TextUtils.isEmpty(codecs)) {
return MimeTypes.AUDIO_UNKNOWN;
} else if (codecs.startsWith("opus")) {
return MimeTypes.AUDIO_OPUS;
}
// TODO: Parse more codecs values here.
return MimeTypes.AUDIO_UNKNOWN;
} }
/* package */ static boolean mimeTypeIsWebm(String mimeType) { /* package */ static boolean mimeTypeIsWebm(String mimeType) {
return mimeType.startsWith(MimeTypes.VIDEO_WEBM) || mimeType.startsWith(MimeTypes.AUDIO_WEBM); return mimeType.startsWith(MimeTypes.VIDEO_WEBM) || mimeType.startsWith(MimeTypes.AUDIO_WEBM)
|| mimeType.startsWith(MimeTypes.APPLICATION_WEBM);
} }
/* package */ static boolean mimeTypeIsRawText(String mimeType) { /* package */ static boolean mimeTypeIsRawText(String mimeType) {

View File

@ -25,6 +25,7 @@ public final class MimeTypes {
public static final String BASE_TYPE_TEXT = "text"; public static final String BASE_TYPE_TEXT = "text";
public static final String BASE_TYPE_APPLICATION = "application"; public static final String BASE_TYPE_APPLICATION = "application";
public static final String VIDEO_UNKNOWN = BASE_TYPE_VIDEO + "/x-unknown";
public static final String VIDEO_MP4 = BASE_TYPE_VIDEO + "/mp4"; public static final String VIDEO_MP4 = BASE_TYPE_VIDEO + "/mp4";
public static final String VIDEO_WEBM = BASE_TYPE_VIDEO + "/webm"; public static final String VIDEO_WEBM = BASE_TYPE_VIDEO + "/webm";
public static final String VIDEO_H263 = BASE_TYPE_VIDEO + "/3gpp"; public static final String VIDEO_H263 = BASE_TYPE_VIDEO + "/3gpp";
@ -34,25 +35,25 @@ public final class MimeTypes {
public static final String VIDEO_VP9 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp9"; public static final String VIDEO_VP9 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp9";
public static final String VIDEO_MP4V = BASE_TYPE_VIDEO + "/mp4v-es"; public static final String VIDEO_MP4V = BASE_TYPE_VIDEO + "/mp4v-es";
public static final String AUDIO_UNKNOWN = BASE_TYPE_AUDIO + "/x-unknown";
public static final String AUDIO_MP4 = BASE_TYPE_AUDIO + "/mp4"; public static final String AUDIO_MP4 = BASE_TYPE_AUDIO + "/mp4";
public static final String AUDIO_AAC = BASE_TYPE_AUDIO + "/mp4a-latm"; public static final String AUDIO_AAC = BASE_TYPE_AUDIO + "/mp4a-latm";
public static final String AUDIO_WEBM = BASE_TYPE_AUDIO + "/webm"; public static final String AUDIO_WEBM = BASE_TYPE_AUDIO + "/webm";
public static final String AUDIO_MPEG = BASE_TYPE_AUDIO + "/mpeg"; public static final String AUDIO_MPEG = BASE_TYPE_AUDIO + "/mpeg";
public static final String AUDIO_MPEG_L1 = BASE_TYPE_AUDIO + "/mpeg-L1"; public static final String AUDIO_MPEG_L1 = BASE_TYPE_AUDIO + "/mpeg-L1";
public static final String AUDIO_MPEG_L2 = BASE_TYPE_AUDIO + "/mpeg-L2"; public static final String AUDIO_MPEG_L2 = BASE_TYPE_AUDIO + "/mpeg-L2";
public static final String AUDIO_RAW = BASE_TYPE_AUDIO + "/raw"; public static final String AUDIO_RAW = BASE_TYPE_AUDIO + "/raw";
public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3"; public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3";
public static final String AUDIO_EC3 = BASE_TYPE_AUDIO + "/eac3"; public static final String AUDIO_EC3 = BASE_TYPE_AUDIO + "/eac3";
public static final String AUDIO_DTS = BASE_TYPE_AUDIO + "/x-dts"; public static final String AUDIO_DTS = BASE_TYPE_AUDIO + "/x-dts";
public static final String AUDIO_DTS_HD = BASE_TYPE_AUDIO + "/vnd.dts.hd"; public static final String AUDIO_DTS_HD = BASE_TYPE_AUDIO + "/vnd.dts.hd";
public static final String AUDIO_VORBIS = BASE_TYPE_AUDIO + "/vorbis"; public static final String AUDIO_VORBIS = BASE_TYPE_AUDIO + "/vorbis";
public static final String AUDIO_OPUS = BASE_TYPE_AUDIO + "/opus"; public static final String AUDIO_OPUS = BASE_TYPE_AUDIO + "/opus";
public static final String TEXT_VTT = BASE_TYPE_TEXT + "/vtt"; public static final String TEXT_VTT = BASE_TYPE_TEXT + "/vtt";
public static final String APPLICATION_MP4 = BASE_TYPE_APPLICATION + "/mp4"; public static final String APPLICATION_MP4 = BASE_TYPE_APPLICATION + "/mp4";
public static final String APPLICATION_WEBM = BASE_TYPE_APPLICATION + "/webm";
public static final String APPLICATION_ID3 = BASE_TYPE_APPLICATION + "/id3"; public static final String APPLICATION_ID3 = BASE_TYPE_APPLICATION + "/id3";
public static final String APPLICATION_EIA608 = BASE_TYPE_APPLICATION + "/eia-608"; public static final String APPLICATION_EIA608 = BASE_TYPE_APPLICATION + "/eia-608";
public static final String APPLICATION_SUBRIP = BASE_TYPE_APPLICATION + "/x-subrip"; public static final String APPLICATION_SUBRIP = BASE_TYPE_APPLICATION + "/x-subrip";
@ -62,20 +63,6 @@ public final class MimeTypes {
private MimeTypes() {} private MimeTypes() {}
/**
* Returns the top-level type of {@code mimeType}.
*
* @param mimeType The mimeType whose top-level type is required.
* @return The top-level type.
*/
public static String getTopLevelType(String mimeType) {
int indexOfSlash = mimeType.indexOf('/');
if (indexOfSlash == -1) {
throw new IllegalArgumentException("Invalid mime type: " + mimeType);
}
return mimeType.substring(0, indexOfSlash);
}
/** /**
* Whether the top-level type of {@code mimeType} is audio. * Whether the top-level type of {@code mimeType} is audio.
* *
@ -116,4 +103,18 @@ public final class MimeTypes {
return getTopLevelType(mimeType).equals(BASE_TYPE_APPLICATION); return getTopLevelType(mimeType).equals(BASE_TYPE_APPLICATION);
} }
/**
* Returns the top-level type of {@code mimeType}.
*
* @param mimeType The mimeType whose top-level type is required.
* @return The top-level type.
*/
private static String getTopLevelType(String mimeType) {
int indexOfSlash = mimeType.indexOf('/');
if (indexOfSlash == -1) {
throw new IllegalArgumentException("Invalid mime type: " + mimeType);
}
return mimeType.substring(0, indexOfSlash);
}
} }