下面列出了android.media.MediaCrypto#com.google.android.exoplayer2.mediacodec.MediaCodecInfo 实例代码,或者点击链接到github查看源代码,也可以在右侧发表评论。
@Override
protected void configureCodec(
MediaCodecInfo codecInfo,
MediaCodec codec,
Format format,
@Nullable MediaCrypto crypto,
float codecOperatingRate) {
codecMaxInputSize = getCodecMaxInputSize(codecInfo, format, getStreamFormats());
codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name);
codecNeedsEosBufferTimestampWorkaround = codecNeedsEosBufferTimestampWorkaround(codecInfo.name);
passthroughEnabled = codecInfo.passthrough;
String codecMimeType = passthroughEnabled ? MimeTypes.AUDIO_RAW : codecInfo.codecMimeType;
MediaFormat mediaFormat =
getMediaFormat(format, codecMimeType, codecMaxInputSize, codecOperatingRate);
codec.configure(mediaFormat, /* surface= */ null, crypto, /* flags= */ 0);
if (passthroughEnabled) {
// Store the input MIME type if we're using the passthrough codec.
passthroughMediaFormat = mediaFormat;
passthroughMediaFormat.setString(MediaFormat.KEY_MIME, format.sampleMimeType);
} else {
passthroughMediaFormat = null;
}
}
@Override
protected void configureCodec(
MediaCodecInfo codecInfo,
MediaCodec codec,
Format format,
MediaCrypto crypto,
float codecOperatingRate) {
String codecMimeType = codecInfo.codecMimeType;
codecMaxValues = getCodecMaxValues(codecInfo, format, getStreamFormats());
MediaFormat mediaFormat =
getMediaFormat(
format,
codecMimeType,
codecMaxValues,
codecOperatingRate,
deviceNeedsNoPostProcessWorkaround,
tunnelingAudioSessionId);
if (surface == null) {
Assertions.checkState(shouldUseDummySurface(codecInfo));
if (dummySurface == null) {
dummySurface = DummySurface.newInstanceV17(context, codecInfo.secure);
}
surface = dummySurface;
}
codec.configure(mediaFormat, surface, crypto, 0);
if (Util.SDK_INT >= 23 && tunneling) {
tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codec);
}
}
/**
* Returns a maximum input size suitable for configuring a codec for {@code format} in a way that
* will allow possible adaptation to other compatible formats in {@code streamFormats}.
*
* @param codecInfo A {@link MediaCodecInfo} describing the decoder.
* @param format The {@link Format} for which the codec is being configured.
* @param streamFormats The possible stream formats.
* @return A suitable maximum input size.
*/
protected int getCodecMaxInputSize(
MediaCodecInfo codecInfo, Format format, Format[] streamFormats) {
int maxInputSize = getCodecMaxInputSize(codecInfo, format);
if (streamFormats.length == 1) {
// The single entry in streamFormats must correspond to the format for which the codec is
// being configured.
return maxInputSize;
}
for (Format streamFormat : streamFormats) {
if (codecInfo.isSeamlessAdaptationSupported(
format, streamFormat, /* isNewFormatComplete= */ false)) {
maxInputSize = Math.max(maxInputSize, getCodecMaxInputSize(codecInfo, streamFormat));
}
}
return maxInputSize;
}
/**
* Returns a maximum input buffer size for a given {@link MediaCodec} and {@link Format}.
*
* @param codecInfo Information about the {@link MediaCodec} being configured.
* @param format The format.
* @return A maximum input buffer size in bytes, or {@link Format#NO_VALUE} if a maximum could not
* be determined.
*/
private static int getMaxInputSize(MediaCodecInfo codecInfo, Format format) {
if (format.maxInputSize != Format.NO_VALUE) {
// The format defines an explicit maximum input size. Add the total size of initialization
// data buffers, as they may need to be queued in the same input buffer as the largest sample.
int totalInitializationDataSize = 0;
int initializationDataCount = format.initializationData.size();
for (int i = 0; i < initializationDataCount; i++) {
totalInitializationDataSize += format.initializationData.get(i).length;
}
return format.maxInputSize + totalInitializationDataSize;
} else {
// Calculated maximum input sizes are overestimates, so it's not necessary to add the size of
// initialization data.
return getCodecMaxInputSize(codecInfo, format.sampleMimeType, format.width, format.height);
}
}
/**
* Returns a maximum input size suitable for configuring a codec for {@code format} in a way that
* will allow possible adaptation to other compatible formats in {@code streamFormats}.
*
* @param codecInfo A {@link MediaCodecInfo} describing the decoder.
* @param format The format for which the codec is being configured.
* @param streamFormats The possible stream formats.
* @return A suitable maximum input size.
*/
protected int getCodecMaxInputSize(
MediaCodecInfo codecInfo, Format format, Format[] streamFormats) {
int maxInputSize = getCodecMaxInputSize(codecInfo, format);
// if (streamFormats.length == 1) {
// // The single entry in streamFormats must correspond to the format for which the codec is
// // being configured.
// return maxInputSize;
// }
// for (Format streamFormat : streamFormats) {
// if (areAdaptationCompatible(format, streamFormat)) {
// maxInputSize = Math.max(maxInputSize, getCodecMaxInputSize(codecInfo, streamFormat));
// }
// }
return maxInputSize;
}
/**
* Returns a maximum input buffer size for a given format.
*
* @param codecInfo A {@link MediaCodecInfo} describing the decoder.
* @param format The format.
* @return A maximum input buffer size in bytes, or {@link Format#NO_VALUE} if a maximum could not
* be determined.
*/
private int getCodecMaxInputSize(MediaCodecInfo codecInfo, Format format) {
if (Util.SDK_INT < 24 && "OMX.google.raw.decoder".equals(codecInfo.name)) {
// OMX.google.raw.decoder didn't resize its output buffers correctly prior to N, so there's no
// point requesting a non-default input size. Doing so may cause a native crash, where-as not
// doing so will cause a more controlled failure when attempting to fill an input buffer. See:
// https://github.com/google/ExoPlayer/issues/4057.
boolean needsRawDecoderWorkaround = true;
if (Util.SDK_INT == 23) {
PackageManager packageManager = context.getPackageManager();
if (packageManager != null
&& packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
// The workaround is not required for AndroidTV devices running M.
needsRawDecoderWorkaround = false;
}
}
if (needsRawDecoderWorkaround) {
return Format.NO_VALUE;
}
}
return format.maxInputSize;
}
/**
* Returns a maximum input buffer size for a given codec and format.
*
* @param codecInfo Information about the {@link MediaCodec} being configured.
* @param format The format.
* @return A maximum input buffer size in bytes, or {@link Format#NO_VALUE} if a maximum could not
* be determined.
*/
private static int getMaxInputSize(MediaCodecInfo codecInfo, Format format) {
if (format.maxInputSize != Format.NO_VALUE) {
// The format defines an explicit maximum input size. Add the total size of initialization
// data buffers, as they may need to be queued in the same input buffer as the largest sample.
int totalInitializationDataSize = 0;
int initializationDataCount = format.initializationData.size();
for (int i = 0; i < initializationDataCount; i++) {
totalInitializationDataSize += format.initializationData.get(i).length;
}
return format.maxInputSize + totalInitializationDataSize;
} else {
// Calculated maximum input sizes are overestimates, so it's not necessary to add the size of
// initialization data.
return getCodecMaxInputSize(codecInfo, format.sampleMimeType, format.width, format.height);
}
}
/**
* Returns a maximum input buffer size for a given codec and format.
*
* @param codecInfo Information about the {@link MediaCodec} being configured.
* @param format The format.
* @return A maximum input buffer size in bytes, or {@link Format#NO_VALUE} if a maximum could not
* be determined.
*/
private static int getMaxInputSize(MediaCodecInfo codecInfo, Format format) {
if (format.maxInputSize != Format.NO_VALUE) {
// The format defines an explicit maximum input size. Add the total size of initialization
// data buffers, as they may need to be queued in the same input buffer as the largest sample.
int totalInitializationDataSize = 0;
int initializationDataCount = format.initializationData.size();
for (int i = 0; i < initializationDataCount; i++) {
totalInitializationDataSize += format.initializationData.get(i).length;
}
return format.maxInputSize + totalInitializationDataSize;
} else {
// Calculated maximum input sizes are overestimates, so it's not necessary to add the size of
// initialization data.
return getMaxInputSize(codecInfo, format.sampleMimeType, format.width, format.height);
}
}
@Override
protected void configureCodec(
MediaCodecInfo codecInfo,
MediaCodec codec,
Format format,
MediaCrypto crypto,
float codecOperatingRate) {
codecMaxInputSize = getCodecMaxInputSize(codecInfo, format, getStreamFormats());
codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name);
passthroughEnabled = codecInfo.passthrough;
String codecMimeType = codecInfo.mimeType == null ? MimeTypes.AUDIO_RAW : codecInfo.mimeType;
MediaFormat mediaFormat =
getMediaFormat(format, codecMimeType, codecMaxInputSize, codecOperatingRate);
codec.configure(mediaFormat, /* surface= */ null, crypto, /* flags= */ 0);
if (passthroughEnabled) {
// Store the input MIME type if we're using the passthrough codec.
passthroughMediaFormat = mediaFormat;
passthroughMediaFormat.setString(MediaFormat.KEY_MIME, format.sampleMimeType);
} else {
passthroughMediaFormat = null;
}
}
/**
* Returns a maximum input size suitable for configuring a codec for {@code format} in a way that
* will allow possible adaptation to other compatible formats in {@code streamFormats}.
*
* @param codecInfo A {@link MediaCodecInfo} describing the decoder.
* @param format The format for which the codec is being configured.
* @param streamFormats The possible stream formats.
* @return A suitable maximum input size.
*/
protected int getCodecMaxInputSize(
MediaCodecInfo codecInfo, Format format, Format[] streamFormats) {
int maxInputSize = getCodecMaxInputSize(codecInfo, format);
// if (streamFormats.length == 1) {
// // The single entry in streamFormats must correspond to the format for which the codec is
// // being configured.
// return maxInputSize;
// }
// for (Format streamFormat : streamFormats) {
// if (areAdaptationCompatible(format, streamFormat)) {
// maxInputSize = Math.max(maxInputSize, getCodecMaxInputSize(codecInfo, streamFormat));
// }
// }
return maxInputSize;
}
/**
* Returns a maximum input buffer size for a given format.
*
* @param codecInfo A {@link MediaCodecInfo} describing the decoder.
* @param format The format.
* @return A maximum input buffer size in bytes, or {@link Format#NO_VALUE} if a maximum could not
* be determined.
*/
private int getCodecMaxInputSize(MediaCodecInfo codecInfo, Format format) {
if (Util.SDK_INT < 24 && "OMX.google.raw.decoder".equals(codecInfo.name)) {
// OMX.google.raw.decoder didn't resize its output buffers correctly prior to N, so there's no
// point requesting a non-default input size. Doing so may cause a native crash, where-as not
// doing so will cause a more controlled failure when attempting to fill an input buffer. See:
// https://github.com/google/ExoPlayer/issues/4057.
boolean needsRawDecoderWorkaround = true;
if (Util.SDK_INT == 23) {
PackageManager packageManager = context.getPackageManager();
if (packageManager != null
&& packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
// The workaround is not required for AndroidTV devices running M.
needsRawDecoderWorkaround = false;
}
}
if (needsRawDecoderWorkaround) {
return Format.NO_VALUE;
}
}
return format.maxInputSize;
}
@Override
protected @KeepCodecResult int canKeepCodec(
MediaCodec codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
if (areAdaptationCompatible(codecInfo.adaptive, oldFormat, newFormat)
&& newFormat.width <= codecMaxValues.width
&& newFormat.height <= codecMaxValues.height
&& getMaxInputSize(codecInfo, newFormat) <= codecMaxValues.inputSize) {
return oldFormat.initializationDataEquals(newFormat)
? KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION
: KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION;
}
return KEEP_CODEC_RESULT_NO;
}
@Override
protected @KeepCodecResult int canKeepCodec(
MediaCodec codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
// TODO: We currently rely on recreating the codec when encoder delay or padding is non-zero.
// Re-creating the codec is necessary to guarantee that onOutputFormatChanged is called, which
// is where encoder delay and padding are propagated to the sink. We should find a better way to
// propagate these values, and then allow the codec to be re-used in cases where this would
// otherwise be possible.
if (getCodecMaxInputSize(codecInfo, newFormat) > codecMaxInputSize
|| oldFormat.encoderDelay != 0
|| oldFormat.encoderPadding != 0
|| newFormat.encoderDelay != 0
|| newFormat.encoderPadding != 0) {
return KEEP_CODEC_RESULT_NO;
} else if (codecInfo.isSeamlessAdaptationSupported(
oldFormat, newFormat, /* isNewFormatComplete= */ true)) {
return KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION;
} else if (areCodecConfigurationCompatible(oldFormat, newFormat)) {
return KEEP_CODEC_RESULT_YES_WITH_FLUSH;
} else {
return KEEP_CODEC_RESULT_NO;
}
}
@Override
protected int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format)
throws DecoderQueryException {
String mimeType = format.sampleMimeType;
if (!MimeTypes.isAudio(mimeType)) {
return FORMAT_UNSUPPORTED_TYPE;
}
int tunnelingSupport = Util.SDK_INT >= 21 ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED;
if (allowPassthrough(mimeType) && mediaCodecSelector.getPassthroughDecoderInfo() != null) {
return ADAPTIVE_NOT_SEAMLESS | tunnelingSupport | FORMAT_HANDLED;
}
MediaCodecInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType, false);
if (decoderInfo == null) {
return FORMAT_UNSUPPORTED_SUBTYPE;
}
// Note: We assume support for unknown sampleRate and channelCount.
boolean decoderCapable = Util.SDK_INT < 21
|| ((format.sampleRate == Format.NO_VALUE
|| decoderInfo.isAudioSampleRateSupportedV21(format.sampleRate))
&& (format.channelCount == Format.NO_VALUE
|| decoderInfo.isAudioChannelCountSupportedV21(format.channelCount)));
int formatSupport = decoderCapable ? FORMAT_HANDLED : FORMAT_EXCEEDS_CAPABILITIES;
return ADAPTIVE_NOT_SEAMLESS | tunnelingSupport | formatSupport;
}
@Override
protected List<MediaCodecInfo> getDecoderInfos(
MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder)
throws DecoderQueryException {
if (allowPassthrough(format.channelCount, format.sampleMimeType)) {
MediaCodecInfo passthroughDecoderInfo = mediaCodecSelector.getPassthroughDecoderInfo();
if (passthroughDecoderInfo != null) {
return Collections.singletonList(passthroughDecoderInfo);
}
}
List<MediaCodecInfo> decoderInfos =
mediaCodecSelector.getDecoderInfos(
format.sampleMimeType, requiresSecureDecoder, /* requiresTunnelingDecoder= */ false);
if (MimeTypes.AUDIO_E_AC3_JOC.equals(format.sampleMimeType)) {
// E-AC3 decoders can decode JOC streams, but in 2-D rather than 3-D.
List<MediaCodecInfo> decoderInfosWithEac3 = new ArrayList<>(decoderInfos);
decoderInfosWithEac3.addAll(
mediaCodecSelector.getDecoderInfos(
MimeTypes.AUDIO_E_AC3, requiresSecureDecoder, /* requiresTunnelingDecoder= */ false));
decoderInfos = decoderInfosWithEac3;
}
return Collections.unmodifiableList(decoderInfos);
}
@Override
protected void configureCodec(
MediaCodecInfo codecInfo,
MediaCodec codec,
Format format,
MediaCrypto crypto,
float codecOperatingRate) {
codecMaxInputSize = getCodecMaxInputSize(codecInfo, format, getStreamFormats());
codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name);
codecNeedsEosBufferTimestampWorkaround = codecNeedsEosBufferTimestampWorkaround(codecInfo.name);
passthroughEnabled = codecInfo.passthrough;
String codecMimeType = passthroughEnabled ? MimeTypes.AUDIO_RAW : codecInfo.codecMimeType;
MediaFormat mediaFormat =
getMediaFormat(format, codecMimeType, codecMaxInputSize, codecOperatingRate);
codec.configure(mediaFormat, /* surface= */ null, crypto, /* flags= */ 0);
if (passthroughEnabled) {
// Store the input MIME type if we're using the passthrough codec.
passthroughMediaFormat = mediaFormat;
passthroughMediaFormat.setString(MediaFormat.KEY_MIME, format.sampleMimeType);
} else {
passthroughMediaFormat = null;
}
}
@Override
protected @KeepCodecResult int canKeepCodec(
MediaCodec codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
// TODO: We currently rely on recreating the codec when encoder delay or padding is non-zero.
// Re-creating the codec is necessary to guarantee that onOutputFormatChanged is called, which
// is where encoder delay and padding are propagated to the sink. We should find a better way to
// propagate these values, and then allow the codec to be re-used in cases where this would
// otherwise be possible.
if (getCodecMaxInputSize(codecInfo, newFormat) > codecMaxInputSize
|| oldFormat.encoderDelay != 0
|| oldFormat.encoderPadding != 0
|| newFormat.encoderDelay != 0
|| newFormat.encoderPadding != 0) {
return KEEP_CODEC_RESULT_NO;
} else if (codecInfo.isSeamlessAdaptationSupported(
oldFormat, newFormat, /* isNewFormatComplete= */ true)) {
return KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION;
} else if (areCodecConfigurationCompatible(oldFormat, newFormat)) {
return KEEP_CODEC_RESULT_YES_WITH_FLUSH;
} else {
return KEEP_CODEC_RESULT_NO;
}
}
/**
* Returns a maximum input size suitable for configuring a codec for {@code format} in a way that
* will allow possible adaptation to other compatible formats in {@code streamFormats}.
*
* @param codecInfo A {@link MediaCodecInfo} describing the decoder.
* @param format The format for which the codec is being configured.
* @param streamFormats The possible stream formats.
* @return A suitable maximum input size.
*/
protected int getCodecMaxInputSize(
MediaCodecInfo codecInfo, Format format, Format[] streamFormats) {
int maxInputSize = getCodecMaxInputSize(codecInfo, format);
if (streamFormats.length == 1) {
// The single entry in streamFormats must correspond to the format for which the codec is
// being configured.
return maxInputSize;
}
for (Format streamFormat : streamFormats) {
if (codecInfo.isSeamlessAdaptationSupported(
format, streamFormat, /* isNewFormatComplete= */ false)) {
maxInputSize = Math.max(maxInputSize, getCodecMaxInputSize(codecInfo, streamFormat));
}
}
return maxInputSize;
}
@Override
protected @KeepCodecResult int canKeepCodec(
MediaCodec codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
if (codecInfo.isSeamlessAdaptationSupported(
oldFormat, newFormat, /* isNewFormatComplete= */ true)
&& newFormat.width <= codecMaxValues.width
&& newFormat.height <= codecMaxValues.height
&& getMaxInputSize(codecInfo, newFormat) <= codecMaxValues.inputSize) {
return oldFormat.initializationDataEquals(newFormat)
? KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION
: KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION;
}
return KEEP_CODEC_RESULT_NO;
}
/**
* Returns a maximum input buffer size for a given codec and format.
*
* @param codecInfo Information about the {@link MediaCodec} being configured.
* @param format The format.
* @return A maximum input buffer size in bytes, or {@link Format#NO_VALUE} if a maximum could not
* be determined.
*/
private static int getMaxInputSize(MediaCodecInfo codecInfo, Format format) {
if (format.maxInputSize != Format.NO_VALUE) {
// The format defines an explicit maximum input size. Add the total size of initialization
// data buffers, as they may need to be queued in the same input buffer as the largest sample.
int totalInitializationDataSize = 0;
int initializationDataCount = format.initializationData.size();
for (int i = 0; i < initializationDataCount; i++) {
totalInitializationDataSize += format.initializationData.get(i).length;
}
return format.maxInputSize + totalInitializationDataSize;
} else {
// Calculated maximum input sizes are overestimates, so it's not necessary to add the size of
// initialization data.
return getCodecMaxInputSize(codecInfo, format.sampleMimeType, format.width, format.height);
}
}
@Override
protected List<MediaCodecInfo> getDecoderInfos(
MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder)
throws DecoderQueryException {
if (allowPassthrough(format.channelCount, format.sampleMimeType)) {
MediaCodecInfo passthroughDecoderInfo = mediaCodecSelector.getPassthroughDecoderInfo();
if (passthroughDecoderInfo != null) {
return Collections.singletonList(passthroughDecoderInfo);
}
}
List<MediaCodecInfo> decoderInfos =
mediaCodecSelector.getDecoderInfos(
format.sampleMimeType, requiresSecureDecoder, /* requiresTunnelingDecoder= */ false);
if (MimeTypes.AUDIO_E_AC3_JOC.equals(format.sampleMimeType)) {
// E-AC3 decoders can decode JOC streams, but in 2-D rather than 3-D.
List<MediaCodecInfo> decoderInfosWithEac3 = new ArrayList<>(decoderInfos);
decoderInfosWithEac3.addAll(
mediaCodecSelector.getDecoderInfos(
MimeTypes.AUDIO_E_AC3, requiresSecureDecoder, /* requiresTunnelingDecoder= */ false));
decoderInfos = decoderInfosWithEac3;
}
return Collections.unmodifiableList(decoderInfos);
}
@Override
protected void configureCodec(
MediaCodecInfo codecInfo,
MediaCodec codec,
Format format,
MediaCrypto crypto,
float codecOperatingRate) {
codecMaxInputSize = getCodecMaxInputSize(codecInfo, format, getStreamFormats());
codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name);
codecNeedsEosBufferTimestampWorkaround = codecNeedsEosBufferTimestampWorkaround(codecInfo.name);
passthroughEnabled = codecInfo.passthrough;
String codecMimeType = passthroughEnabled ? MimeTypes.AUDIO_RAW : codecInfo.codecMimeType;
MediaFormat mediaFormat =
getMediaFormat(format, codecMimeType, codecMaxInputSize, codecOperatingRate);
codec.configure(mediaFormat, /* surface= */ null, crypto, /* flags= */ 0);
if (passthroughEnabled) {
// Store the input MIME type if we're using the passthrough codec.
passthroughMediaFormat = mediaFormat;
passthroughMediaFormat.setString(MediaFormat.KEY_MIME, format.sampleMimeType);
} else {
passthroughMediaFormat = null;
}
}
/**
* Returns a maximum video size to use when configuring a codec for {@code format} in a way that
* will allow possible adaptation to other compatible formats that are expected to have the same
* aspect ratio, but whose sizes are unknown.
*
* @param codecInfo Information about the {@link MediaCodec} being configured.
* @param format The format for which the codec is being configured.
* @return The maximum video size to use, or null if the size of {@code format} should be used.
*/
private static Point getCodecMaxSize(MediaCodecInfo codecInfo, Format format) {
boolean isVerticalVideo = format.height > format.width;
int formatLongEdgePx = isVerticalVideo ? format.height : format.width;
int formatShortEdgePx = isVerticalVideo ? format.width : format.height;
float aspectRatio = (float) formatShortEdgePx / formatLongEdgePx;
for (int longEdgePx : STANDARD_LONG_EDGE_VIDEO_PX) {
int shortEdgePx = (int) (longEdgePx * aspectRatio);
if (longEdgePx <= formatLongEdgePx || shortEdgePx <= formatShortEdgePx) {
// Don't return a size not larger than the format for which the codec is being configured.
return null;
} else if (Util.SDK_INT >= 21) {
Point alignedSize = codecInfo.alignVideoSizeV21(isVerticalVideo ? shortEdgePx : longEdgePx,
isVerticalVideo ? longEdgePx : shortEdgePx);
float frameRate = format.frameRate;
if (codecInfo.isVideoSizeAndRateSupportedV21(alignedSize.x, alignedSize.y, frameRate)) {
return alignedSize;
}
} else {
try {
// Conservatively assume the codec requires 16px width and height alignment.
longEdgePx = Util.ceilDivide(longEdgePx, 16) * 16;
shortEdgePx = Util.ceilDivide(shortEdgePx, 16) * 16;
if (longEdgePx * shortEdgePx <= MediaCodecUtil.maxH264DecodableFrameSize()) {
return new Point(
isVerticalVideo ? shortEdgePx : longEdgePx,
isVerticalVideo ? longEdgePx : shortEdgePx);
}
} catch (DecoderQueryException e) {
// We tried our best. Give up!
return null;
}
}
}
return null;
}
@Override
protected List<MediaCodecInfo> getDecoderInfos(
MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder)
throws DecoderQueryException {
@Nullable String mimeType = format.sampleMimeType;
if (mimeType == null) {
return Collections.emptyList();
}
if (allowPassthrough(format.channelCount, mimeType)) {
@Nullable
MediaCodecInfo passthroughDecoderInfo = mediaCodecSelector.getPassthroughDecoderInfo();
if (passthroughDecoderInfo != null) {
return Collections.singletonList(passthroughDecoderInfo);
}
}
List<MediaCodecInfo> decoderInfos =
mediaCodecSelector.getDecoderInfos(
mimeType, requiresSecureDecoder, /* requiresTunnelingDecoder= */ false);
decoderInfos = MediaCodecUtil.getDecoderInfosSortedByFormatSupport(decoderInfos, format);
if (MimeTypes.AUDIO_E_AC3_JOC.equals(mimeType)) {
// E-AC3 decoders can decode JOC streams, but in 2-D rather than 3-D.
List<MediaCodecInfo> decoderInfosWithEac3 = new ArrayList<>(decoderInfos);
decoderInfosWithEac3.addAll(
mediaCodecSelector.getDecoderInfos(
MimeTypes.AUDIO_E_AC3, requiresSecureDecoder, /* requiresTunnelingDecoder= */ false));
decoderInfos = decoderInfosWithEac3;
}
return Collections.unmodifiableList(decoderInfos);
}
/**
* Returns a maximum input buffer size for a given {@link Format}.
*
* @param codecInfo A {@link MediaCodecInfo} describing the decoder.
* @param format The {@link Format}.
* @return A maximum input buffer size in bytes, or {@link Format#NO_VALUE} if a maximum could not
* be determined.
*/
private int getCodecMaxInputSize(MediaCodecInfo codecInfo, Format format) {
if ("OMX.google.raw.decoder".equals(codecInfo.name)) {
// OMX.google.raw.decoder didn't resize its output buffers correctly prior to N, except on
// Android TV running M, so there's no point requesting a non-default input size. Doing so may
// cause a native crash, whereas not doing so will cause a more controlled failure when
// attempting to fill an input buffer. See: https://github.com/google/ExoPlayer/issues/4057.
if (Util.SDK_INT < 24 && !(Util.SDK_INT == 23 && Util.isTv(context))) {
return Format.NO_VALUE;
}
}
return format.maxInputSize;
}
private static List<MediaCodecInfo> getDecoderInfos(
MediaCodecSelector mediaCodecSelector,
Format format,
boolean requiresSecureDecoder,
boolean requiresTunnelingDecoder)
throws DecoderQueryException {
@Nullable String mimeType = format.sampleMimeType;
if (mimeType == null) {
return Collections.emptyList();
}
List<MediaCodecInfo> decoderInfos =
mediaCodecSelector.getDecoderInfos(
mimeType, requiresSecureDecoder, requiresTunnelingDecoder);
decoderInfos = MediaCodecUtil.getDecoderInfosSortedByFormatSupport(decoderInfos, format);
if (MimeTypes.VIDEO_DOLBY_VISION.equals(mimeType)) {
// Fall back to H.264/AVC or H.265/HEVC for the relevant DV profiles.
@Nullable
Pair<Integer, Integer> codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format);
if (codecProfileAndLevel != null) {
int profile = codecProfileAndLevel.first;
if (profile == CodecProfileLevel.DolbyVisionProfileDvheDtr
|| profile == CodecProfileLevel.DolbyVisionProfileDvheSt) {
decoderInfos.addAll(
mediaCodecSelector.getDecoderInfos(
MimeTypes.VIDEO_H265, requiresSecureDecoder, requiresTunnelingDecoder));
} else if (profile == CodecProfileLevel.DolbyVisionProfileDvavSe) {
decoderInfos.addAll(
mediaCodecSelector.getDecoderInfos(
MimeTypes.VIDEO_H264, requiresSecureDecoder, requiresTunnelingDecoder));
}
}
}
return Collections.unmodifiableList(decoderInfos);
}
@Override
protected void configureCodec(
MediaCodecInfo codecInfo,
MediaCodec codec,
Format format,
@Nullable MediaCrypto crypto,
float codecOperatingRate) {
String codecMimeType = codecInfo.codecMimeType;
codecMaxValues = getCodecMaxValues(codecInfo, format, getStreamFormats());
MediaFormat mediaFormat =
getMediaFormat(
format,
codecMimeType,
codecMaxValues,
codecOperatingRate,
deviceNeedsNoPostProcessWorkaround,
tunnelingAudioSessionId);
if (surface == null) {
Assertions.checkState(shouldUseDummySurface(codecInfo));
if (dummySurface == null) {
dummySurface = DummySurface.newInstanceV17(context, codecInfo.secure);
}
surface = dummySurface;
}
codec.configure(mediaFormat, surface, crypto, 0);
if (Util.SDK_INT >= 23 && tunneling) {
tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codec);
}
}
@Override
protected @KeepCodecResult int canKeepCodec(
MediaCodec codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
if (codecInfo.isSeamlessAdaptationSupported(
oldFormat, newFormat, /* isNewFormatComplete= */ true)
&& newFormat.width <= codecMaxValues.width
&& newFormat.height <= codecMaxValues.height
&& getMaxInputSize(codecInfo, newFormat) <= codecMaxValues.inputSize) {
return oldFormat.initializationDataEquals(newFormat)
? KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION
: KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION;
}
return KEEP_CODEC_RESULT_NO;
}
/**
* Returns a maximum video size to use when configuring a codec for {@code format} in a way that
* will allow possible adaptation to other compatible formats that are expected to have the same
* aspect ratio, but whose sizes are unknown.
*
* @param codecInfo Information about the {@link MediaCodec} being configured.
* @param format The {@link Format} for which the codec is being configured.
* @return The maximum video size to use, or null if the size of {@code format} should be used.
*/
private static Point getCodecMaxSize(MediaCodecInfo codecInfo, Format format) {
boolean isVerticalVideo = format.height > format.width;
int formatLongEdgePx = isVerticalVideo ? format.height : format.width;
int formatShortEdgePx = isVerticalVideo ? format.width : format.height;
float aspectRatio = (float) formatShortEdgePx / formatLongEdgePx;
for (int longEdgePx : STANDARD_LONG_EDGE_VIDEO_PX) {
int shortEdgePx = (int) (longEdgePx * aspectRatio);
if (longEdgePx <= formatLongEdgePx || shortEdgePx <= formatShortEdgePx) {
// Don't return a size not larger than the format for which the codec is being configured.
return null;
} else if (Util.SDK_INT >= 21) {
Point alignedSize = codecInfo.alignVideoSizeV21(isVerticalVideo ? shortEdgePx : longEdgePx,
isVerticalVideo ? longEdgePx : shortEdgePx);
float frameRate = format.frameRate;
if (codecInfo.isVideoSizeAndRateSupportedV21(alignedSize.x, alignedSize.y, frameRate)) {
return alignedSize;
}
} else {
try {
// Conservatively assume the codec requires 16px width and height alignment.
longEdgePx = Util.ceilDivide(longEdgePx, 16) * 16;
shortEdgePx = Util.ceilDivide(shortEdgePx, 16) * 16;
if (longEdgePx * shortEdgePx <= MediaCodecUtil.maxH264DecodableFrameSize()) {
return new Point(
isVerticalVideo ? shortEdgePx : longEdgePx,
isVerticalVideo ? longEdgePx : shortEdgePx);
}
} catch (DecoderQueryException e) {
// We tried our best. Give up!
return null;
}
}
}
return null;
}
@Override
protected List<MediaCodecInfo> getDecoderInfos(
MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder)
throws DecoderQueryException {
if (allowPassthrough(format.sampleMimeType)) {
MediaCodecInfo passthroughDecoderInfo = mediaCodecSelector.getPassthroughDecoderInfo();
if (passthroughDecoderInfo != null) {
return Collections.singletonList(passthroughDecoderInfo);
}
}
return super.getDecoderInfos(mediaCodecSelector, format, requiresSecureDecoder);
}