Implement decoder capability checks in MediaCodecTrackRenderers.

[Step 4 - Partial, of []

- The capabilities checks previously performed in VideoFormatSelectorUtil
  are now performed in MediaCodecVideoTrackRenderer. This means they'll be
  useful for non-chunk use cases (e.g. when using ExtractorSampleSource).
- Added capabilities checks for audio in MediaCodecAudioTrackRenderer. We
  didn't check audio capabilities previously.
- Added functionality to allow a TrackRenderer to indicate the extent of
  its adaptive support.

The idea here is that a TrackSelector (to be introduced) will have access to:

(a) TrackGroups from the SampleSource that indicate whether they support
adaptive playbacks and the formats of each individual track.
(b) TrackRenderers that indicate whether they support adaptive playbacks as
well as how capable they are of rendering formats of individual tracks.

This is everything that a TrackSelector needs from the player components in
order to decide how to wire things up. Note that a TrackSelector may opt to
treat FORMAT_EXCEEDS_CAPABILITIES as FORMAT_HANDLED at its own risk, if it
thinks that it (or the user) knows better. This is a request that we've seen
from third parties for better handling cases where capabilities aren't
accurately reported by the underlying platform.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=114427428
This commit is contained in:
olly 2016-02-11 05:35:02 -08:00 committed by Oliver Woodman
parent 6bc52262db
commit 335bb0aff2
14 changed files with 319 additions and 411 deletions

View File

@ -15,9 +15,17 @@
*/
package com.google.android.exoplayer;
import com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi;
import android.media.MediaCodecInfo;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCodecInfo.CodecProfileLevel;
/**
* Contains information about a media decoder.
*/
@TargetApi(16)
public final class DecoderInfo {
/**
@ -36,13 +44,120 @@ public final class DecoderInfo {
*/
public final boolean adaptive;
private final CodecCapabilities capabilities;
/**
* @param name The name of the decoder.
* @param adaptive Whether the decoder is adaptive.
*/
/* package */ DecoderInfo(String name, boolean adaptive) {
/* package */ DecoderInfo(String name) {
this.name = name;
this.adaptive = adaptive;
this.adaptive = false;
this.capabilities = null;
}
/**
* @param name The name of the decoder.
* @param capabilities The capabilities of the decoder.
*/
/* package */ DecoderInfo(String name, CodecCapabilities capabilities) {
this.name = name;
this.capabilities = capabilities;
adaptive = isAdaptive(capabilities);
}
/**
* The profile levels supported by the decoder.
*
* @return The profile levels supported by the decoder.
*/
public CodecProfileLevel[] getProfileLevels() {
return capabilities == null || capabilities.profileLevels == null ? new CodecProfileLevel[0]
: capabilities.profileLevels;
}
/**
* Whether the decoder supports video with a specified width and height.
* <p>
* Must not be called if the device SDK version is less than 21.
*
* @param width Width in pixels.
* @param height Height in pixels.
* @return Whether the decoder supports video with the given width and height.
*/
@TargetApi(21)
public boolean isVideoSizeSupportedV21(int width, int height) {
if (capabilities == null) {
return false;
}
MediaCodecInfo.VideoCapabilities videoCapabilities = capabilities.getVideoCapabilities();
return videoCapabilities != null && videoCapabilities.isSizeSupported(width, height);
}
/**
* Whether the decoder supports video with a given width, height and frame rate.
* <p>
* Must not be called if the device SDK version is less than 21.
*
* @param width Width in pixels.
* @param height Height in pixels.
* @param frameRate Frame rate in frames per second.
* @return Whether the decoder supports video with the given width, height and frame rate.
*/
@TargetApi(21)
public boolean isVideoSizeAndRateSupportedV21(int width, int height, double frameRate) {
if (capabilities == null) {
return false;
}
MediaCodecInfo.VideoCapabilities videoCapabilities = capabilities.getVideoCapabilities();
return videoCapabilities != null && videoCapabilities.areSizeAndRateSupported(width, height,
frameRate);
}
/**
* Whether the decoder supports audio with a given sample rate.
* <p>
* Must not be called if the device SDK version is less than 21.
*
* @param sampleRate The sample rate in Hz.
* @return Whether the decoder supports audio with the given sample rate.
*/
@TargetApi(21)
public boolean isAudioSampleRateSupportedV21(int sampleRate) {
if (capabilities == null) {
return false;
}
MediaCodecInfo.AudioCapabilities audioCapabilities = capabilities.getAudioCapabilities();
return audioCapabilities != null && audioCapabilities.isSampleRateSupported(sampleRate);
}
/**
* Whether the decoder supports audio with a given channel count.
* <p>
* Must not be called if the device SDK version is less than 21.
*
* @param channelCount The channel count.
* @return Whether the decoder supports audio with the given channel count.
*/
@TargetApi(21)
public boolean isAudioChannelCountSupportedV21(int channelCount) {
if (capabilities == null) {
return false;
}
MediaCodecInfo.AudioCapabilities audioCapabilities = capabilities.getAudioCapabilities();
return audioCapabilities != null && audioCapabilities.getMaxInputChannelCount() >= channelCount;
}
private static boolean isAdaptive(CodecCapabilities capabilities) {
if (Util.SDK_INT >= 19) {
return isAdaptiveV19(capabilities);
} else {
return false;
}
}
@TargetApi(19)
private static boolean isAdaptiveV19(CodecCapabilities capabilities) {
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback);
}
}

View File

@ -21,8 +21,8 @@ package com.google.android.exoplayer;
public final class DummyTrackRenderer extends TrackRenderer {
@Override
protected boolean handlesTrack(MediaFormat mediaFormat) throws ExoPlaybackException {
return false;
protected int supportsFormat(MediaFormat mediaFormat) throws ExoPlaybackException {
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
}
@Override

View File

@ -328,7 +328,7 @@ import java.util.concurrent.atomic.AtomicInteger;
MediaFormat adaptiveTrackFormat = null;
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
MediaFormat trackFormat = source.getTrackGroup(groupIndex).getFormat(trackIndex);
if (renderer.handlesTrack(trackFormat)) {
if (renderer.supportsFormat(trackFormat) == TrackRenderer.FORMAT_HANDLED) {
adaptiveTrackIndices[adaptiveTrackIndexCount++] = trackIndex;
if (adaptiveTrackFormat == null) {
adaptiveTrackFormat = trackFormat.copyAsAdaptive("auto");
@ -345,7 +345,7 @@ import java.util.concurrent.atomic.AtomicInteger;
}
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
MediaFormat trackFormat = source.getTrackGroup(groupIndex).getFormat(trackIndex);
if (renderer.handlesTrack(trackFormat)) {
if (renderer.supportsFormat(trackFormat) == TrackRenderer.FORMAT_HANDLED) {
rendererTrackGroups[rendererTrackCount] = groupIndex;
rendererTrackIndices[rendererTrackCount] = new int[] {trackIndex};
rendererTrackFormats[rendererTrackCount++] = trackFormat;

View File

@ -20,6 +20,7 @@ import com.google.android.exoplayer.audio.AudioCapabilities;
import com.google.android.exoplayer.audio.AudioTrack;
import com.google.android.exoplayer.drm.DrmSessionManager;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi;
import android.media.AudioManager;
@ -179,12 +180,34 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
}
@Override
protected boolean handlesTrack(MediaCodecSelector mediaCodecSelector, MediaFormat mediaFormat)
protected int supportsFormat(MediaCodecSelector mediaCodecSelector, MediaFormat mediaFormat)
throws DecoderQueryException {
String mimeType = mediaFormat.mimeType;
return MimeTypes.isAudio(mimeType) && (MimeTypes.AUDIO_UNKNOWN.equals(mimeType)
|| (allowPassthrough(mimeType) && mediaCodecSelector.getPassthroughDecoderName() != null)
|| mediaCodecSelector.getDecoderInfo(mediaFormat, false) != null);
if (!MimeTypes.isAudio(mimeType)) {
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
}
if (allowPassthrough(mimeType) && mediaCodecSelector.getPassthroughDecoderName() != null) {
return TrackRenderer.FORMAT_HANDLED;
}
// TODO[REFACTOR]: Propagate requiresSecureDecoder to this point. Note that we need to check
// that the drm session can make use of a secure decoder, as well as that a secure decoder
// exists.
DecoderInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mediaFormat.mimeType, false);
if (decoderInfo == null) {
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
}
if (Util.SDK_INT >= 21) {
// Note: We assume support in the case that the sampleRate or channelCount is unknown.
if (mediaFormat.sampleRate != MediaFormat.NO_VALUE
&& !decoderInfo.isAudioSampleRateSupportedV21(mediaFormat.sampleRate)) {
return TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES;
}
if (mediaFormat.channelCount != MediaFormat.NO_VALUE
&& !decoderInfo.isAudioChannelCountSupportedV21(mediaFormat.channelCount)) {
return TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES;
}
}
return TrackRenderer.FORMAT_HANDLED;
}
@Override
@ -194,7 +217,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
String passthroughDecoderName = mediaCodecSelector.getPassthroughDecoderName();
if (passthroughDecoderName != null) {
passthroughEnabled = true;
return new DecoderInfo(passthroughDecoderName, false);
return new DecoderInfo(passthroughDecoderName);
}
}
passthroughEnabled = false;

View File

@ -27,7 +27,7 @@ public interface MediaCodecSelector {
/**
* Default implementation of {@link MediaCodecSelector}.
*/
public static final MediaCodecSelector DEFAULT = new MediaCodecSelector() {
MediaCodecSelector DEFAULT = new MediaCodecSelector() {
/**
* The name for the raw (passthrough) decoder OMX component.
@ -35,9 +35,9 @@ public interface MediaCodecSelector {
private static final String RAW_DECODER_NAME = "OMX.google.raw.decoder";
@Override
public DecoderInfo getDecoderInfo(MediaFormat format, boolean requiresSecureDecoder)
public DecoderInfo getDecoderInfo(String mimeType, boolean requiresSecureDecoder)
throws DecoderQueryException {
return MediaCodecUtil.getDecoderInfo(format.mimeType, requiresSecureDecoder);
return MediaCodecUtil.getDecoderInfo(mimeType, requiresSecureDecoder);
}
@Override
@ -49,15 +49,15 @@ public interface MediaCodecSelector {
};
/**
* Selects a decoder to instantiate for a given format.
* Selects a decoder to instantiate for a given mime type.
*
* @param format The format for which a decoder is required.
* @param mimeType The mime type for which a decoder is required.
* @param requiresSecureDecoder Whether a secure decoder is required.
* @return A {@link DecoderInfo} describing the decoder to instantiate, or null if no suitable
* decoder exists.
* @throws DecoderQueryException Thrown if there was an error querying decoders.
*/
DecoderInfo getDecoderInfo(MediaFormat format, boolean requiresSecureDecoder)
DecoderInfo getDecoderInfo(String mimeType, boolean requiresSecureDecoder)
throws DecoderQueryException;
/**

View File

@ -262,23 +262,40 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
}
@Override
protected final boolean handlesTrack(MediaFormat mediaFormat) throws ExoPlaybackException {
protected final int supportsAdaptive(String mimeType) throws ExoPlaybackException {
if (mimeType == null) {
return TrackRenderer.ADAPTIVE_NOT_SEAMLESS;
}
try {
return handlesTrack(mediaCodecSelector, mediaFormat);
// TODO[REFACTOR]: Propagate requiresSecureDecoder to this point.
DecoderInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType, false);
return decoderInfo != null && decoderInfo.adaptive ? TrackRenderer.ADAPTIVE_SEAMLESS
: TrackRenderer.ADAPTIVE_NOT_SEAMLESS;
} catch (DecoderQueryException e) {
throw new ExoPlaybackException(e);
}
}
@Override
protected final int supportsFormat(MediaFormat mediaFormat) throws ExoPlaybackException {
try {
return supportsFormat(mediaCodecSelector, mediaFormat);
} catch (DecoderQueryException e) {
throw new ExoPlaybackException(e);
}
}
/**
* Returns whether this renderer is capable of handling the provided track.
* Returns the extent to which the renderer is capable of rendering a given format.
*
* @param mediaCodecSelector The decoder selector.
* @param mediaFormat The format of the track.
* @return True if the renderer can handle the track, false otherwise.
* @throws DecoderQueryException Thrown if there was an error querying decoders.
* @param mediaFormat The format.
* @return The extent to which the renderer is capable of rendering the given format. One of
* {@link #FORMAT_HANDLED}, {@link #FORMAT_EXCEEDS_CAPABILITIES} and
* {@link #FORMAT_UNSUPPORTED_TYPE}.
* @throws DecoderQueryException If there was an error querying decoders.
*/
protected abstract boolean handlesTrack(MediaCodecSelector mediaCodecSelector,
protected abstract int supportsFormat(MediaCodecSelector mediaCodecSelector,
MediaFormat mediaFormat) throws DecoderQueryException;
/**
@ -293,7 +310,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
*/
protected DecoderInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector,
MediaFormat mediaFormat, boolean requiresSecureDecoder) throws DecoderQueryException {
return mediaCodecSelector.getDecoderInfo(format, requiresSecureDecoder);
return mediaCodecSelector.getDecoderInfo(format.mimeType, requiresSecureDecoder);
}
/**

View File

@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.Util;
@ -26,7 +25,6 @@ import android.media.MediaCodecInfo.CodecProfileLevel;
import android.media.MediaCodecList;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import java.util.HashMap;
@ -52,29 +50,12 @@ public final class MediaCodecUtil {
private static final String TAG = "MediaCodecUtil";
private static final HashMap<CodecKey, Pair<String, CodecCapabilities>> codecs = new HashMap<>();
private static final HashMap<CodecKey, DecoderInfo> codecs = new HashMap<>();
private MediaCodecUtil() {}
/**
* Get information about the decoder that will be used for a given mime type.
*
* @param mimeType The mime type.
* @param secure Whether the decoder is required to support secure decryption. Always pass false
* unless secure decryption really is required.
* @return Information about the decoder that will be used, or null if no decoder exists.
*/
public static DecoderInfo getDecoderInfo(String mimeType, boolean secure)
throws DecoderQueryException {
Pair<String, CodecCapabilities> info = getMediaCodecInfo(mimeType, secure);
if (info == null) {
return null;
}
return new DecoderInfo(info.first, isAdaptive(info.second));
}
/**
* Optional call to warm the codec cache for a given mime type.
* Optional call to pre-populate cached decoder information for a given mime type.
* <p>
* Calling this method may speed up subsequent calls to {@link #getDecoderInfo(String, boolean)}.
*
@ -82,9 +63,9 @@ public final class MediaCodecUtil {
* @param secure Whether the decoder is required to support secure decryption. Always pass false
* unless secure decryption really is required.
*/
public static synchronized void warmCodec(String mimeType, boolean secure) {
public static synchronized void warmDecoderInfoCache(String mimeType, boolean secure) {
try {
getMediaCodecInfo(mimeType, secure);
getDecoderInfo(mimeType, secure);
} catch (DecoderQueryException e) {
// Codec warming is best effort, so we can swallow the exception.
Log.e(TAG, "Codec warming failed", e);
@ -92,42 +73,41 @@ public final class MediaCodecUtil {
}
/**
* Returns the name of the best decoder and its capabilities for the given mimeType.
* Returns a {@link DecoderInfo} describing the most suitable decoder for a given mime type.
*
* @param mimeType The mime type.
* @param secure Whether the decoder is required to support secure decryption. Always pass false
* unless secure decryption really is required.
* @return The name of the best decoder and its capabilities for the given mimeType, or null if
* no decoder exists.
* @return Information about the decoder, or null if no suitable decoder exists.
*/
public static synchronized Pair<String, CodecCapabilities> getMediaCodecInfo(
String mimeType, boolean secure) throws DecoderQueryException {
public static synchronized DecoderInfo getDecoderInfo(String mimeType, boolean secure)
throws DecoderQueryException {
CodecKey key = new CodecKey(mimeType, secure);
if (codecs.containsKey(key)) {
return codecs.get(key);
}
MediaCodecListCompat mediaCodecList = Util.SDK_INT >= 21
? new MediaCodecListCompatV21(secure) : new MediaCodecListCompatV16();
Pair<String, CodecCapabilities> codecInfo = getMediaCodecInfo(key, mediaCodecList);
if (secure && codecInfo == null && 21 <= Util.SDK_INT && Util.SDK_INT <= 23) {
DecoderInfo info = getDecoderInfo(key, mediaCodecList);
if (secure && info == null && 21 <= Util.SDK_INT && Util.SDK_INT <= 23) {
// Some devices don't list secure decoders on API level 21 [Internal: b/18678462]. Try the
// legacy path. We also try this path on API levels 22 and 23 as a defensive measure.
// TODO: Verify that the issue cannot occur on API levels 22 and 23, and tighten this block
// to execute on API level 21 only if confirmed.
mediaCodecList = new MediaCodecListCompatV16();
codecInfo = getMediaCodecInfo(key, mediaCodecList);
if (codecInfo != null) {
info = getDecoderInfo(key, mediaCodecList);
if (info != null) {
Log.w(TAG, "MediaCodecList API didn't list secure decoder for: " + mimeType
+ ". Assuming: " + codecInfo.first);
+ ". Assuming: " + info.name);
}
}
return codecInfo;
return info;
}
private static Pair<String, CodecCapabilities> getMediaCodecInfo(CodecKey key,
MediaCodecListCompat mediaCodecList) throws DecoderQueryException {
private static DecoderInfo getDecoderInfo(CodecKey key, MediaCodecListCompat mediaCodecList)
throws DecoderQueryException {
try {
return getMediaCodecInfoInternal(key, mediaCodecList);
return getDecoderInfoInternal(key, mediaCodecList);
} catch (Exception e) {
// If the underlying mediaserver is in a bad state, we may catch an IllegalStateException
// or an IllegalArgumentException here.
@ -135,7 +115,7 @@ public final class MediaCodecUtil {
}
}
private static Pair<String, CodecCapabilities> getMediaCodecInfoInternal(CodecKey key,
private static DecoderInfo getDecoderInfoInternal(CodecKey key,
MediaCodecListCompat mediaCodecList) {
String mimeType = key.mimeType;
int numberOfCodecs = mediaCodecList.getCodecCount();
@ -154,16 +134,16 @@ public final class MediaCodecUtil {
if (!secureDecodersExplicit) {
// Cache variants for both insecure and (if we think it's supported) secure playback.
codecs.put(key.secure ? new CodecKey(mimeType, false) : key,
Pair.create(codecName, capabilities));
new DecoderInfo(codecName, capabilities));
if (secure) {
codecs.put(key.secure ? key : new CodecKey(mimeType, true),
Pair.create(codecName + ".secure", capabilities));
new DecoderInfo(codecName + ".secure", capabilities));
}
} else {
// Only cache this variant. If both insecure and secure decoders are available, they
// should both be listed separately.
codecs.put(key.secure == secure ? key : new CodecKey(mimeType, secure),
Pair.create(codecName, capabilities));
new DecoderInfo(codecName, capabilities));
}
if (codecs.containsKey(key)) {
return codecs.get(key);
@ -233,79 +213,23 @@ public final class MediaCodecUtil {
return true;
}
private static boolean isAdaptive(CodecCapabilities capabilities) {
if (Util.SDK_INT >= 19) {
return isAdaptiveV19(capabilities);
} else {
return false;
}
}
@TargetApi(19)
private static boolean isAdaptiveV19(CodecCapabilities capabilities) {
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback);
}
/**
* Tests whether the device advertises it can decode video of a given type at a specified width
* and height.
* <p>
* Must not be called if the device SDK version is less than 21.
* Whether the default H264 decoder supports the specified profile at the specified level.
*
* @param mimeType The mime type.
* @param secure Whether the decoder is required to support secure decryption. Always pass false
* unless secure decryption really is required.
* @param width Width in pixels.
* @param height Height in pixels.
* @return Whether the decoder advertises support of the given size.
*/
@TargetApi(21)
public static boolean isSizeSupportedV21(String mimeType, boolean secure, int width,
int height) throws DecoderQueryException {
Assertions.checkState(Util.SDK_INT >= 21);
MediaCodecInfo.VideoCapabilities videoCapabilities = getVideoCapabilitiesV21(mimeType, secure);
return videoCapabilities != null && videoCapabilities.isSizeSupported(width, height);
}
/**
* Tests whether the device advertises it can decode video of a given type at a specified
* width, height, and frame rate.
* <p>
* Must not be called if the device SDK version is less than 21.
*
* @param mimeType The mime type.
* @param secure Whether the decoder is required to support secure decryption. Always pass false
* unless secure decryption really is required.
* @param width Width in pixels.
* @param height Height in pixels.
* @param frameRate Frame rate in frames per second.
* @return Whether the decoder advertises support of the given size and frame rate.
*/
@TargetApi(21)
public static boolean isSizeAndRateSupportedV21(String mimeType, boolean secure,
int width, int height, double frameRate) throws DecoderQueryException {
Assertions.checkState(Util.SDK_INT >= 21);
MediaCodecInfo.VideoCapabilities videoCapabilities = getVideoCapabilitiesV21(mimeType, secure);
return videoCapabilities != null
&& videoCapabilities.areSizeAndRateSupported(width, height, frameRate);
}
/**
* @param profile An AVC profile constant from {@link CodecProfileLevel}.
* @param level An AVC profile level from {@link CodecProfileLevel}.
* @param profile A profile constant from {@link CodecProfileLevel}.
* @param level A profile level from {@link CodecProfileLevel}.
* @return Whether the specified profile is supported at the specified level.
*/
public static boolean isH264ProfileSupported(int profile, int level)
throws DecoderQueryException {
Pair<String, CodecCapabilities> info = getMediaCodecInfo(MimeTypes.VIDEO_H264, false);
DecoderInfo info = getDecoderInfo(MimeTypes.VIDEO_H264, false);
if (info == null) {
return false;
}
CodecCapabilities capabilities = info.second;
for (int i = 0; i < capabilities.profileLevels.length; i++) {
CodecProfileLevel profileLevel = capabilities.profileLevels[i];
if (profileLevel.profile == profile && profileLevel.level >= level) {
CodecProfileLevel[] profileLevels = info.getProfileLevels();
for (int i = 0; i < profileLevels.length; i++) {
if (profileLevels[i].profile == profile && profileLevels[i].level >= level) {
return true;
}
}
@ -314,35 +238,27 @@ public final class MediaCodecUtil {
}
/**
* @return the maximum frame size for an H264 stream that can be decoded on the device.
* Returns the maximum frame size supported by the default H264 decoder.
*
* @return The maximum frame size for an H264 stream that can be decoded on the device.
*/
public static int maxH264DecodableFrameSize() throws DecoderQueryException {
Pair<String, CodecCapabilities> info = getMediaCodecInfo(MimeTypes.VIDEO_H264, false);
DecoderInfo info = getDecoderInfo(MimeTypes.VIDEO_H264, false);
if (info == null) {
return 0;
}
int maxH264DecodableFrameSize = 0;
CodecCapabilities capabilities = info.second;
for (int i = 0; i < capabilities.profileLevels.length; i++) {
CodecProfileLevel profileLevel = capabilities.profileLevels[i];
maxH264DecodableFrameSize = Math.max(
avcLevelToMaxFrameSize(profileLevel.level), maxH264DecodableFrameSize);
CodecProfileLevel[] profileLevels = info.getProfileLevels();
for (int i = 0; i < profileLevels.length; i++) {
CodecProfileLevel profileLevel = profileLevels[i];
maxH264DecodableFrameSize = Math.max(avcLevelToMaxFrameSize(profileLevel.level),
maxH264DecodableFrameSize);
}
return maxH264DecodableFrameSize;
}
@TargetApi(21)
private static MediaCodecInfo.VideoCapabilities getVideoCapabilitiesV21(String mimeType,
boolean secure) throws DecoderQueryException {
Pair<String, CodecCapabilities> info = getMediaCodecInfo(mimeType, secure);
if (info == null) {
return null;
}
return info.second.getVideoCapabilities();
}
/**
* Conversion values taken from: https://en.wikipedia.org/wiki/H.264/MPEG-4_AVC.
*

View File

@ -213,11 +213,38 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
}
@Override
protected boolean handlesTrack(MediaCodecSelector mediaCodecSelector, MediaFormat mediaFormat)
protected int supportsFormat(MediaCodecSelector mediaCodecSelector, MediaFormat mediaFormat)
throws DecoderQueryException {
String mimeType = mediaFormat.mimeType;
return MimeTypes.isVideo(mimeType) && (MimeTypes.VIDEO_UNKNOWN.equals(mimeType)
|| mediaCodecSelector.getDecoderInfo(mediaFormat, false) != null);
if (!MimeTypes.isVideo(mimeType)) {
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
}
// TODO[REFACTOR]: Propagate requiresSecureDecoder to this point. Note that we need to check
// that the drm session can make use of a secure decoder, as well as that a secure decoder
// exists.
DecoderInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mediaFormat.mimeType, false);
if (decoderInfo == null) {
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
}
if (mediaFormat.width > 0 && mediaFormat.height > 0) {
if (Util.SDK_INT >= 21) {
// TODO[REFACTOR]: Propagate frame rate to this point.
// if (mediaFormat.frameRate > 0) {
// return decoderInfo.isSizeAndRateSupportedV21(mediaFormat.width, mediaFormat.height,
// mediaFormat.frameRate) ? TrackRenderer.TRACK_HANDLED
// : TrackRenderer.TRACK_NOT_HANDLED_EXCEEDS_CAPABILITIES;
// } else {
return decoderInfo.isVideoSizeSupportedV21(mediaFormat.width, mediaFormat.height)
? TrackRenderer.FORMAT_HANDLED : TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES;
// }
}
// TODO[REFACTOR]: We should probably assume that we can decode at least the resolution of
// the display, or the camera, as a sanity check?
if (mediaFormat.width * mediaFormat.height > MediaCodecUtil.maxH264DecodableFrameSize()) {
return TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES;
}
}
return TrackRenderer.FORMAT_HANDLED;
}
@Override

View File

@ -46,11 +46,11 @@ public final class TrackGroup {
}
/**
* @param supportsAdaptive Whether it's possible to adapt between multiple tracks in the group.
* @param adaptive Whether it's possible to adapt between multiple tracks in the group.
* @param formats The track formats.
*/
public TrackGroup(boolean supportsAdaptive, MediaFormat... formats) {
this.adaptive = supportsAdaptive;
public TrackGroup(boolean adaptive, MediaFormat... formats) {
this.adaptive = adaptive;
this.formats = formats;
length = formats.length;
}

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer;
import com.google.android.exoplayer.ExoPlayer.ExoPlayerComponent;
import com.google.android.exoplayer.SampleSource.TrackStream;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes;
import java.io.IOException;
@ -34,6 +35,42 @@ import java.io.IOException;
*/
public abstract class TrackRenderer implements ExoPlayerComponent {
/**
* The {@link TrackRenderer} can seamlessly adapt between formats.
*/
public static final int ADAPTIVE_SEAMLESS = 0;
/**
* The {@link TrackRenderer} can adapt between formats, but may suffer a brief discontinuity
* (~50-100ms) when adaptation occurs.
*/
public static final int ADAPTIVE_NOT_SEAMLESS = 1;
/**
* The {@link TrackRenderer} does not support adaptation between formats.
*/
public static final int ADAPTIVE_NOT_SUPPORTED = 2;
/**
* The {@link TrackRenderer} is capable of rendering the format.
*/
public static final int FORMAT_HANDLED = 0;
/**
* The {@link TrackRenderer} is capable of rendering formats with the same mimeType, but the
* properties of the format exceed the renderer's capability.
* <p>
* Example: The {@link TrackRenderer} is capable of rendering H264 and the format's mimeType is
* {@link MimeTypes#VIDEO_H264}, but the format's resolution exceeds the maximum limit supported
* by the underlying H264 decoder.
*/
public static final int FORMAT_EXCEEDS_CAPABILITIES = 1;
/**
* The {@link TrackRenderer} is not capable of rendering the format, or any other format with the
* same mimeType.
* <p>
* Example: The {@link TrackRenderer} is only capable of rendering video and the track has an
* audio mimeType.
*/
public static final int FORMAT_UNSUPPORTED_TYPE = 2;
/**
* The renderer is idle.
*/
@ -75,13 +112,31 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
}
/**
* Returns whether this renderer is capable of handling the provided track.
* Returns the extent to which the renderer is capable of consuming from a {@link TrackStream}
* that adapts between multiple supported formats of a given mimeType, or of differing mimeTypes
* if {@code mimeType == null} is passed.
*
* @param mediaFormat The format of the track.
* @return True if the renderer can handle the track, false otherwise.
* @param mimeType The mimeType, or null to query the extent to which the renderer is capable of
* adapting between formats with different mimeTypes.
* @return The extent to which the renderer supports the adaptation. One of
* {@link #ADAPTIVE_SEAMLESS}, {@link #ADAPTIVE_NOT_SEAMLESS} and
* {@link #ADAPTIVE_NOT_SUPPORTED}.
* @throws ExoPlaybackException If an error occurs.
*/
protected abstract boolean handlesTrack(MediaFormat mediaFormat) throws ExoPlaybackException;
protected int supportsAdaptive(String mimeType) throws ExoPlaybackException {
return ADAPTIVE_NOT_SUPPORTED;
}
/**
* Returns the extent to which the renderer is capable of rendering a given format.
*
* @param mediaFormat The format.
* @return The extent to which the renderer is capable of rendering the given format. One of
* {@link #FORMAT_HANDLED}, {@link #FORMAT_EXCEEDS_CAPABILITIES} and
* {@link #FORMAT_UNSUPPORTED_TYPE}.
* @throws ExoPlaybackException If an error occurs.
*/
protected abstract int supportsFormat(MediaFormat mediaFormat) throws ExoPlaybackException;
/**
* Enable the renderer to consume from the specified {@link TrackStream}.

View File

@ -1,248 +0,0 @@
/*
* 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;
import com.google.android.exoplayer.MediaCodecUtil;
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Point;
import android.view.Display;
import android.view.WindowManager;
import java.util.ArrayList;
import java.util.List;
/**
* Selects from possible video formats.
*/
public final class VideoFormatSelectorUtil {
/**
* If a dimension (i.e. width or height) of a video is greater or equal to this fraction of the
* corresponding viewport dimension, then the video is considered as filling the viewport (in that
* dimension).
*/
private static final float FRACTION_TO_CONSIDER_FULLSCREEN = 0.98f;
/**
* Chooses a suitable subset from a number of video formats, to be rendered on the device's
* default display.
*
* @param context A context.
* @param formatWrappers Wrapped formats from which to select.
* @param allowedContainerMimeTypes An array of allowed container mime types. Null allows all
* mime types.
* @param filterHdFormats True to filter HD formats. False otherwise.
* @return An array holding the indices of the selected formats.
* @throws DecoderQueryException Thrown if there was an error querying decoders.
*/
public static int[] selectVideoFormatsForDefaultDisplay(Context context,
List<? extends FormatWrapper> formatWrappers, String[] allowedContainerMimeTypes,
boolean filterHdFormats) throws DecoderQueryException {
Point viewportSize = getViewportSize(context);
return selectVideoFormats(formatWrappers, allowedContainerMimeTypes, filterHdFormats, true,
viewportSize.x, viewportSize.y);
}
/**
* Chooses a suitable subset from a number of video formats.
* <p>
* A format is filtered (i.e. not selected) if:
* <ul>
* <li>{@code allowedContainerMimeTypes} is non-null and the format does not have one of the
* permitted mime types.
* <li>{@code filterHdFormats} is true and the format is HD.
* <li>It's determined that the video decoder isn't powerful enough to decode the format.
* <li>There exists another format of lower resolution whose resolution exceeds the maximum size
* in pixels that the video can be rendered within the viewport.
* </ul>
*
* @param formatWrappers Wrapped formats from which to select.
* @param allowedContainerMimeTypes An array of allowed container mime types. Null allows all
* mime types.
* @param filterHdFormats True to filter HD formats. False otherwise.
* @param orientationMayChange True if the video's orientation may change with respect to the
* viewport during playback.
* @param viewportWidth The width in pixels of the viewport within which the video will be
* displayed. If the viewport size may change, this should be set to the maximum possible
* width. -1 if selection should not be constrained by a viewport.
* @param viewportHeight The height in pixels of the viewport within which the video will be
* displayed. If the viewport size may change, this should be set to the maximum possible
* height. -1 if selection should not be constrained by a viewport.
* @return An array holding the indices of the selected formats.
* @throws DecoderQueryException
*/
public static int[] selectVideoFormats(List<? extends FormatWrapper> formatWrappers,
String[] allowedContainerMimeTypes, boolean filterHdFormats, boolean orientationMayChange,
int viewportWidth, int viewportHeight) throws DecoderQueryException {
int maxVideoPixelsToRetain = Integer.MAX_VALUE;
ArrayList<Integer> selectedIndexList = new ArrayList<>();
int maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
// First pass to filter out formats that individually fail to meet the selection criteria.
int formatWrapperCount = formatWrappers.size();
for (int i = 0; i < formatWrapperCount; i++) {
Format format = formatWrappers.get(i).getFormat();
if (isFormatPlayable(format, allowedContainerMimeTypes, filterHdFormats,
maxDecodableFrameSize)) {
// Select the format for now. It may still be filtered in the second pass below.
selectedIndexList.add(i);
// Keep track of the number of pixels of the selected format whose resolution is the
// smallest to exceed the maximum size at which it can be displayed within the viewport.
// We'll discard formats of higher resolution in a second pass.
if (format.width > 0 && format.height > 0 && viewportWidth > 0 && viewportHeight > 0) {
Point maxVideoSizeInViewport = getMaxVideoSizeInViewport(orientationMayChange,
viewportWidth, viewportHeight, format.width, format.height);
int videoPixels = format.width * format.height;
if (format.width >= (int) (maxVideoSizeInViewport.x * FRACTION_TO_CONSIDER_FULLSCREEN)
&& format.height >= (int) (maxVideoSizeInViewport.y * FRACTION_TO_CONSIDER_FULLSCREEN)
&& videoPixels < maxVideoPixelsToRetain) {
maxVideoPixelsToRetain = videoPixels;
}
}
}
}
// Second pass to filter out formats that exceed maxVideoPixelsToRetain. These formats are have
// unnecessarily high resolution given the size at which the video will be displayed within the
// viewport.
if (maxVideoPixelsToRetain != Integer.MAX_VALUE) {
for (int i = selectedIndexList.size() - 1; i >= 0; i--) {
Format format = formatWrappers.get(selectedIndexList.get(i)).getFormat();
if (format.width > 0 && format.height > 0
&& format.width * format.height > maxVideoPixelsToRetain) {
selectedIndexList.remove(i);
}
}
}
return Util.toArray(selectedIndexList);
}
/**
* Determines whether an individual format is playable, given an array of allowed container types,
* whether HD formats should be filtered and a maximum decodable frame size in pixels.
*/
private static boolean isFormatPlayable(Format format, String[] allowedContainerMimeTypes,
boolean filterHdFormats, int maxDecodableFrameSize) throws DecoderQueryException {
if (allowedContainerMimeTypes != null
&& !Util.contains(allowedContainerMimeTypes, format.mimeType)) {
// Filtering format based on its container mime type.
return false;
}
if (filterHdFormats && (format.width >= 1280 || format.height >= 720)) {
// Filtering format because it's HD.
return false;
}
if (format.width > 0 && format.height > 0) {
String videoMediaMimeType = MimeTypes.getVideoMediaMimeType(format.codecs);
if (Util.SDK_INT >= 21 && !MimeTypes.VIDEO_UNKNOWN.equals(videoMediaMimeType)) {
if (format.frameRate > 0) {
return MediaCodecUtil.isSizeAndRateSupportedV21(videoMediaMimeType, false, format.width,
format.height, format.frameRate);
} else {
return MediaCodecUtil.isSizeSupportedV21(videoMediaMimeType, false, format.width,
format.height);
}
}
//Assuming that the media is H.264
if (format.width * format.height > maxDecodableFrameSize) {
// Filtering stream that device cannot play
return false;
}
}
return true;
}
/**
* Given viewport dimensions and video dimensions, computes the maximum size of the video as it
* will be rendered to fit inside of the viewport.
*/
private static Point getMaxVideoSizeInViewport(boolean orientationMayChange, int viewportWidth,
int viewportHeight, int videoWidth, int videoHeight) {
if (orientationMayChange && (videoWidth > videoHeight) != (viewportWidth > viewportHeight)) {
// Rotation is allowed, and the video will be larger in the rotated viewport.
int tempViewportWidth = viewportWidth;
viewportWidth = viewportHeight;
viewportHeight = tempViewportWidth;
}
if (videoWidth * viewportHeight >= videoHeight * viewportWidth) {
// Horizontal letter-boxing along top and bottom.
return new Point(viewportWidth, Util.ceilDivide(viewportWidth * videoHeight, videoWidth));
} else {
// Vertical letter-boxing along edges.
return new Point(Util.ceilDivide(viewportHeight * videoWidth, videoHeight), viewportHeight);
}
}
private static Point getViewportSize(Context context) {
// Before API 23 the platform Display object does not provide a way to identify Android TVs that
// can show 4k resolution in a SurfaceView, so check for supported devices here.
// See also https://developer.sony.com/develop/tvs/android-tv/design-guide/.
if (Util.SDK_INT < 23 && Util.MODEL != null && Util.MODEL.startsWith("BRAVIA")
&& context.getPackageManager().hasSystemFeature("com.sony.dtv.hardware.panel.qfhd")) {
return new Point(3840, 2160);
}
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
return getDisplaySize(windowManager.getDefaultDisplay());
}
private static Point getDisplaySize(Display display) {
Point displaySize = new Point();
if (Util.SDK_INT >= 23) {
getDisplaySizeV23(display, displaySize);
} else if (Util.SDK_INT >= 17) {
getDisplaySizeV17(display, displaySize);
} else if (Util.SDK_INT >= 16) {
getDisplaySizeV16(display, displaySize);
} else {
getDisplaySizeV9(display, displaySize);
}
return displaySize;
}
@TargetApi(23)
private static void getDisplaySizeV23(Display display, Point outSize) {
Display.Mode mode = display.getMode();
outSize.x = mode.getPhysicalWidth();
outSize.y = mode.getPhysicalHeight();
}
@TargetApi(17)
private static void getDisplaySizeV17(Display display, Point outSize) {
display.getRealSize(outSize);
}
@TargetApi(16)
private static void getDisplaySizeV16(Display display, Point outSize) {
display.getSize(outSize);
}
@SuppressWarnings("deprecation")
private static void getDisplaySizeV9(Display display, Point outSize) {
outSize.x = display.getWidth();
outSize.y = display.getHeight();
}
private VideoFormatSelectorUtil() {}
}

View File

@ -86,8 +86,9 @@ public final class MetadataTrackRenderer<T> extends SampleSourceTrackRenderer im
}
@Override
protected boolean handlesTrack(MediaFormat mediaFormat) {
return metadataParser.canParse(mediaFormat.mimeType);
protected int supportsFormat(MediaFormat mediaFormat) {
return metadataParser.canParse(mediaFormat.mimeType) ? TrackRenderer.FORMAT_HANDLED
: TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
}
@Override

View File

@ -155,8 +155,9 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement
}
@Override
protected boolean handlesTrack(MediaFormat mediaFormat) {
return getParserIndex(mediaFormat) != -1;
protected int supportsFormat(MediaFormat mediaFormat) {
return getParserIndex(mediaFormat) != -1 ? TrackRenderer.FORMAT_HANDLED
: TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
}
@Override

View File

@ -86,8 +86,9 @@ public final class Eia608TrackRenderer extends SampleSourceTrackRenderer impleme
}
@Override
protected boolean handlesTrack(MediaFormat mediaFormat) {
return eia608Parser.canParse(mediaFormat.mimeType);
protected int supportsFormat(MediaFormat mediaFormat) {
return eia608Parser.canParse(mediaFormat.mimeType) ? TrackRenderer.FORMAT_HANDLED
: TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
}
@Override