mirror of
https://github.com/androidx/media.git
synced 2025-05-03 21:57:46 +08:00
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:
parent
4104a8def9
commit
8c3f93d6bf
@ -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,
|
||||
ChunkSource 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,
|
||||
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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user