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

View File

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

View File

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

View File

@ -52,6 +52,8 @@ import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.SystemClock;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
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 EventListener eventListener;
@ -138,8 +142,8 @@ public class DashChunkSource implements ChunkSource, Output {
*/
public DashChunkSource(DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
long durationMs, int adaptationSetType, Representation... representations) {
this(buildManifest(durationMs, adaptationSetType, Arrays.asList(representations)), null,
dataSource, adaptiveFormatEvaluator);
this(dataSource, adaptiveFormatEvaluator, durationMs, adaptationSetType,
Arrays.asList(representations));
}
/**
@ -522,7 +526,7 @@ public class DashChunkSource implements ChunkSource, Output {
public void adaptiveTrack(MediaPresentationDescription manifest, int periodIndex,
int adaptationSetIndex, int[] representationIndices) {
if (adaptiveFormatEvaluator == null) {
// Do nothing.
Log.w(TAG, "Skipping adaptive track (missing format evaluator)");
return;
}
AdaptationSet adaptationSet = manifest.getPeriod(periodIndex).adaptationSets.get(
@ -542,10 +546,19 @@ public class DashChunkSource implements ChunkSource, Output {
}
Arrays.sort(representationFormats, new DecreasingBandwidthComparator());
long trackDurationUs = manifest.dynamic ? C.UNKNOWN_TIME_US : manifest.duration * 1000;
MediaFormat trackFormat = buildTrackFormat(adaptationSet.type, maxHeightRepresentationFormat,
trackDurationUs).copyAsAdaptive();
tracks.add(new ExposedTrack(trackFormat, adaptationSetIndex, representationFormats, maxWidth,
maxHeight));
String mediaMimeType = getMediaMimeType(maxHeightRepresentationFormat);
if (mediaMimeType == null) {
Log.w(TAG, "Skipped adaptive track (unknown media mime type)");
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
@ -554,8 +567,17 @@ public class DashChunkSource implements ChunkSource, Output {
List<AdaptationSet> adaptationSets = manifest.getPeriod(periodIndex).adaptationSets;
AdaptationSet adaptationSet = adaptationSets.get(adaptationSetIndex);
Format representationFormat = adaptationSet.representations.get(representationIndex).format;
MediaFormat trackFormat = buildTrackFormat(adaptationSet.type, representationFormat,
manifest.dynamic ? C.UNKNOWN_TIME_US : manifest.duration * 1000);
String mediaMimeType = getMediaMimeType(representationFormat);
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));
}
@ -574,34 +596,64 @@ public class DashChunkSource implements ChunkSource, Output {
Collections.singletonList(period));
}
private static MediaFormat buildTrackFormat(int adaptationSetType, Format format,
long durationUs) {
private static MediaFormat getTrackFormat(int adaptationSetType, Format format,
String mediaMimeType, long durationUs) {
switch (adaptationSetType) {
case AdaptationSet.TYPE_VIDEO:
return MediaFormat.createVideoFormat(getMediaMimeType(format), format.bitrate,
MediaFormat.NO_VALUE, durationUs, format.width, format.height, 0, null);
return MediaFormat.createVideoFormat(mediaMimeType, format.bitrate, MediaFormat.NO_VALUE,
durationUs, format.width, format.height, 0, null);
case AdaptationSet.TYPE_AUDIO:
return MediaFormat.createAudioFormat(getMediaMimeType(format), format.bitrate,
MediaFormat.NO_VALUE, durationUs, format.audioChannels, format.audioSamplingRate, null);
return MediaFormat.createAudioFormat(mediaMimeType, format.bitrate, MediaFormat.NO_VALUE,
durationUs, format.audioChannels, format.audioSamplingRate, null);
case AdaptationSet.TYPE_TEXT:
return MediaFormat.createTextFormat(getMediaMimeType(format), format.bitrate,
format.language, durationUs);
return MediaFormat.createTextFormat(mediaMimeType, format.bitrate, format.language,
durationUs);
default:
throw new IllegalStateException("Invalid type: " + adaptationSetType);
return null;
}
}
private static String getMediaMimeType(Format format) {
String mimeType = format.mimeType;
if (MimeTypes.APPLICATION_MP4.equals(format.mimeType) && "stpp".equals(format.codecs)) {
String formatMimeType = format.mimeType;
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;
} 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) {
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) {

View File

@ -25,6 +25,7 @@ public final class MimeTypes {
public static final String BASE_TYPE_TEXT = "text";
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_WEBM = BASE_TYPE_VIDEO + "/webm";
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_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_AAC = BASE_TYPE_AUDIO + "/mp4a-latm";
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_L1 = BASE_TYPE_AUDIO + "/mpeg-L1";
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_AC3 = BASE_TYPE_AUDIO + "/ac3";
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_HD = BASE_TYPE_AUDIO + "/vnd.dts.hd";
public static final String AUDIO_VORBIS = BASE_TYPE_AUDIO + "/vorbis";
public static final String AUDIO_OPUS = BASE_TYPE_AUDIO + "/opus";
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_WEBM = BASE_TYPE_APPLICATION + "/webm";
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_SUBRIP = BASE_TYPE_APPLICATION + "/x-subrip";
@ -62,20 +63,6 @@ public final class 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.
*
@ -116,4 +103,18 @@ public final class MimeTypes {
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);
}
}