@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 int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format) throws DecoderQueryException { String mimeType = format.sampleMimeType; if (!MimeTypes.isAudio(mimeType)) { return FORMAT_UNSUPPORTED_TYPE; } if (allowPassthrough(mimeType) && mediaCodecSelector.getPassthroughDecoderInfo() != null) { return ADAPTIVE_NOT_SEAMLESS | 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 | formatSupport; }
@Override protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format, MediaCrypto crypto) throws DecoderQueryException { codecMaxValues = getCodecMaxValues(codecInfo, format, streamFormats); MediaFormat mediaFormat = getMediaFormat(format, codecMaxValues, deviceNeedsAutoFrcWorkaround, tunnelingAudioSessionId); if (surface == null) { Assertions.checkState(shouldUseDummySurface(codecInfo.secure)); 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); } }
private static boolean shouldSkipAdaptiveTest(String mimeType) throws DecoderQueryException { MediaCodecInfo decoderInfo = MediaCodecUtil.getDecoderInfo(mimeType, false); assertNotNull(decoderInfo); if (decoderInfo.adaptive) { return false; } assertTrue(Util.SDK_INT < 21); return true; }
@Override protected MediaCodecInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder) throws DecoderQueryException { if (allowPassthrough(format.sampleMimeType)) { MediaCodecInfo passthroughDecoderInfo = mediaCodecSelector.getPassthroughDecoderInfo(); if (passthroughDecoderInfo != null) { passthroughEnabled = true; return passthroughDecoderInfo; } } passthroughEnabled = false; return super.getDecoderInfo(mediaCodecSelector, format, requiresSecureDecoder); }
@Override protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format, MediaCrypto crypto) { codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name); if (passthroughEnabled) { // Override the MIME type used to configure the codec if we are using a passthrough decoder. passthroughMediaFormat = format.getFrameworkMediaFormatV16(); passthroughMediaFormat.setString(MediaFormat.KEY_MIME, MimeTypes.AUDIO_RAW); codec.configure(passthroughMediaFormat, null, crypto, 0); passthroughMediaFormat.setString(MediaFormat.KEY_MIME, format.sampleMimeType); } else { codec.configure(format.getFrameworkMediaFormatV16(), null, crypto, 0); passthroughMediaFormat = null; } }
@Override protected int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format) throws DecoderQueryException { String mimeType = format.sampleMimeType; if (!MimeTypes.isVideo(mimeType)) { return FORMAT_UNSUPPORTED_TYPE; } boolean requiresSecureDecryption = false; DrmInitData drmInitData = format.drmInitData; if (drmInitData != null) { for (int i = 0; i < drmInitData.schemeDataCount; i++) { requiresSecureDecryption |= drmInitData.get(i).requiresSecureDecryption; } } MediaCodecInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType, requiresSecureDecryption); if (decoderInfo == null) { return FORMAT_UNSUPPORTED_SUBTYPE; } boolean decoderCapable = decoderInfo.isCodecSupported(format.codecs); if (decoderCapable && format.width > 0 && format.height > 0) { if (Util.SDK_INT >= 21) { decoderCapable = decoderInfo.isVideoSizeAndRateSupportedV21(format.width, format.height, format.frameRate); } else { decoderCapable = format.width * format.height <= MediaCodecUtil.maxH264DecodableFrameSize(); if (!decoderCapable) { Log.d(TAG, "FalseCheck [legacyFrameSize, " + format.width + "x" + format.height + "] [" + Util.DEVICE_DEBUG_INFO + "]"); } } } int adaptiveSupport = decoderInfo.adaptive ? ADAPTIVE_SEAMLESS : ADAPTIVE_NOT_SEAMLESS; int tunnelingSupport = decoderInfo.tunneling ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED; int formatSupport = decoderCapable ? FORMAT_HANDLED : FORMAT_EXCEEDS_CAPABILITIES; return adaptiveSupport | tunnelingSupport | formatSupport; }
@Override protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format, MediaCrypto crypto) throws DecoderQueryException { codecMaxValues = getCodecMaxValues(codecInfo, format, streamFormats); MediaFormat mediaFormat = getMediaFormat(format, codecMaxValues, deviceNeedsAutoFrcWorkaround, tunnelingAudioSessionId); codec.configure(mediaFormat, surface, crypto, 0); if (Util.SDK_INT >= 23 && tunneling) { tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codec); } }
/** * Returns {@link CodecMaxValues} 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 Information about the {@link MediaCodec} being configured. * @param format The format for which the codec is being configured. * @param streamFormats The possible stream formats. * @return Suitable {@link CodecMaxValues}. * @throws DecoderQueryException If an error occurs querying {@code codecInfo}. */ private static CodecMaxValues getCodecMaxValues(MediaCodecInfo codecInfo, Format format, Format[] streamFormats) throws DecoderQueryException { int maxWidth = format.width; int maxHeight = format.height; int maxInputSize = getMaxInputSize(format); if (streamFormats.length == 1) { // The single entry in streamFormats must correspond to the format for which the codec is // being configured. return new CodecMaxValues(maxWidth, maxHeight, maxInputSize); } boolean haveUnknownDimensions = false; for (Format streamFormat : streamFormats) { if (areAdaptationCompatible(codecInfo.adaptive, format, streamFormat)) { haveUnknownDimensions |= (streamFormat.width == Format.NO_VALUE || streamFormat.height == Format.NO_VALUE); maxWidth = Math.max(maxWidth, streamFormat.width); maxHeight = Math.max(maxHeight, streamFormat.height); maxInputSize = Math.max(maxInputSize, getMaxInputSize(streamFormat)); } } if (haveUnknownDimensions) { Log.w(TAG, "Resolutions unknown. Codec max resolution: " + maxWidth + "x" + maxHeight); Point codecMaxSize = getCodecMaxSize(codecInfo, format); if (codecMaxSize != null) { maxWidth = Math.max(maxWidth, codecMaxSize.x); maxHeight = Math.max(maxHeight, codecMaxSize.y); maxInputSize = Math.max(maxInputSize, getMaxInputSize(format.sampleMimeType, maxWidth, maxHeight)); Log.w(TAG, "Codec max resolution adjusted to: " + maxWidth + "x" + maxHeight); } } return new CodecMaxValues(maxWidth, maxHeight, maxInputSize); }
/** * 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. * @throws DecoderQueryException If an error occurs querying {@code codecInfo}. */ private static Point getCodecMaxSize(MediaCodecInfo codecInfo, Format format) throws DecoderQueryException { 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 { // 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); } } } return null; }
/** * Returns {@link CodecMaxValues} 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 Information about the {@link MediaCodec} being configured. * @param format The format for which the codec is being configured. * @param streamFormats The possible stream formats. * @return Suitable {@link CodecMaxValues}. * @throws DecoderQueryException If an error occurs querying {@code codecInfo}. */ private static CodecMaxValues getCodecMaxValues(MediaCodecInfo codecInfo, Format format, Format[] streamFormats) throws DecoderQueryException { int maxWidth = format.width; int maxHeight = format.height; int maxInputSize = getMaxInputSize(format); if (streamFormats.length == 1) { // The single entry in streamFormats must correspond to the format for which the codec is // being configured. return new CodecMaxValues(maxWidth, maxHeight, maxInputSize); } boolean haveUnknownDimensions = false; for (Format streamFormat : streamFormats) { if (areAdaptationCompatible(format, streamFormat)) { haveUnknownDimensions |= (streamFormat.width == Format.NO_VALUE || streamFormat.height == Format.NO_VALUE); maxWidth = Math.max(maxWidth, streamFormat.width); maxHeight = Math.max(maxHeight, streamFormat.height); maxInputSize = Math.max(maxInputSize, getMaxInputSize(streamFormat)); } } if (haveUnknownDimensions) { Log.w(TAG, "Resolutions unknown. Codec max resolution: " + maxWidth + "x" + maxHeight); Point codecMaxSize = getCodecMaxSize(codecInfo, format); if (codecMaxSize != null) { maxWidth = Math.max(maxWidth, codecMaxSize.x); maxHeight = Math.max(maxHeight, codecMaxSize.y); maxInputSize = Math.max(maxInputSize, getMaxInputSize(format.sampleMimeType, maxWidth, maxHeight)); Log.w(TAG, "Codec max resolution adjusted to: " + maxWidth + "x" + maxHeight); } } return new CodecMaxValues(maxWidth, maxHeight, maxInputSize); }
@Override protected int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format) throws DecoderQueryException { String mimeType = format.sampleMimeType; if (!MimeTypes.isVideo(mimeType)) { return FORMAT_UNSUPPORTED_TYPE; } boolean requiresSecureDecryption = false; DrmInitData drmInitData = format.drmInitData; if (drmInitData != null) { for (int i = 0; i < drmInitData.schemeDataCount; i++) { requiresSecureDecryption |= drmInitData.get(i).requiresSecureDecryption; } } MediaCodecInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType, requiresSecureDecryption); if (decoderInfo == null) { return FORMAT_UNSUPPORTED_SUBTYPE; } boolean decoderCapable = decoderInfo.isCodecSupported(format.codecs); if (decoderCapable && format.width > 0 && format.height > 0) { if (Util.SDK_INT >= 21) { if (format.frameRate > 0) { decoderCapable = decoderInfo.isVideoSizeAndRateSupportedV21(format.width, format.height, format.frameRate); } else { decoderCapable = decoderInfo.isVideoSizeSupportedV21(format.width, format.height); } } else { decoderCapable = format.width * format.height <= MediaCodecUtil.maxH264DecodableFrameSize(); } } int adaptiveSupport = decoderInfo.adaptive ? ADAPTIVE_SEAMLESS : ADAPTIVE_NOT_SEAMLESS; int formatSupport = decoderCapable ? FORMAT_HANDLED : FORMAT_EXCEEDS_CAPABILITIES; return adaptiveSupport | formatSupport; }
public void testDecoderInfoH264() throws DecoderQueryException { if (Util.SDK_INT < 16) { // Pass. return; } MediaCodecInfo decoderInfo = MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_H264, false); assertNotNull(decoderInfo); assertTrue(Util.SDK_INT < 21 || decoderInfo.adaptive); }
@Override protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format, MediaCrypto crypto) throws DecoderQueryException { // If the codec is being initialized whilst the renderer is started, default behavior is to // render the first frame (i.e. the keyframe before the current position), then drop frames up // to the current playback position. For test runs that place a maximum limit on the number of // dropped frames allowed, this is not desired behavior. Hence we skip (rather than drop) // frames up to the current playback position [Internal: b/66494991]. skipToPositionBeforeRenderingFirstFrame = getState() == Renderer.STATE_STARTED; super.configureCodec(codecInfo, codec, format, crypto); }
/** * Returns {@link CodecMaxValues} 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 Information about the {@link MediaCodec} being configured. * @param format The format for which the codec is being configured. * @param streamFormats The possible stream formats. * @return Suitable {@link CodecMaxValues}. * @throws DecoderQueryException If an error occurs querying {@code codecInfo}. */ protected CodecMaxValues getCodecMaxValues(MediaCodecInfo codecInfo, Format format, Format[] streamFormats) throws DecoderQueryException { int maxWidth = format.width; int maxHeight = format.height; int maxInputSize = getMaxInputSize(format); if (streamFormats.length == 1) { // The single entry in streamFormats must correspond to the format for which the codec is // being configured. return new CodecMaxValues(maxWidth, maxHeight, maxInputSize); } boolean haveUnknownDimensions = false; for (Format streamFormat : streamFormats) { if (areAdaptationCompatible(codecInfo.adaptive, format, streamFormat)) { haveUnknownDimensions |= (streamFormat.width == Format.NO_VALUE || streamFormat.height == Format.NO_VALUE); maxWidth = Math.max(maxWidth, streamFormat.width); maxHeight = Math.max(maxHeight, streamFormat.height); maxInputSize = Math.max(maxInputSize, getMaxInputSize(streamFormat)); } } if (haveUnknownDimensions) { Log.w(TAG, "Resolutions unknown. Codec max resolution: " + maxWidth + "x" + maxHeight); Point codecMaxSize = getCodecMaxSize(codecInfo, format); if (codecMaxSize != null) { maxWidth = Math.max(maxWidth, codecMaxSize.x); maxHeight = Math.max(maxHeight, codecMaxSize.y); maxInputSize = Math.max(maxInputSize, getMaxInputSize(format.sampleMimeType, maxWidth, maxHeight)); Log.w(TAG, "Codec max resolution adjusted to: " + maxWidth + "x" + maxHeight); } } return new CodecMaxValues(maxWidth, maxHeight, maxInputSize); }
@Override public MediaCodecInfo getDecoderInfo(String mimeType, boolean contentRequiresSecureDecoder) throws MediaCodecUtil.DecoderQueryException { return internalMediaCodecUtil.getDecoderInfo(mimeType, USE_INSECURE_DECODER); }
@Override public MediaCodecInfo getPassthroughDecoderInfo() throws MediaCodecUtil.DecoderQueryException { return internalMediaCodecUtil.getPassthroughDecoderInfo(); }
MediaCodecInfo getDecoderInfo(String mimeType, boolean requiresSecureDecoder) throws MediaCodecUtil.DecoderQueryException { return MediaCodecUtil.getDecoderInfo(mimeType, requiresSecureDecoder); }
MediaCodecInfo getPassthroughDecoderInfo() throws MediaCodecUtil.DecoderQueryException { return MediaCodecUtil.getPassthroughDecoderInfo(); }
private static boolean shouldSkipAdaptiveTest(String mimeType) throws DecoderQueryException { MediaCodecInfo decoderInfo = MediaCodecUtil.getDecoderInfo(mimeType, false); return decoderInfo == null || !decoderInfo.adaptive; }
@Override protected int supportsFormat(MediaCodecSelector mediaCodecSelector, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, Format format) throws DecoderQueryException { String mimeType = format.sampleMimeType; if (!MimeTypes.isVideo(mimeType)) { return FORMAT_UNSUPPORTED_TYPE; } boolean requiresSecureDecryption = false; DrmInitData drmInitData = format.drmInitData; if (drmInitData != null) { for (int i = 0; i < drmInitData.schemeDataCount; i++) { requiresSecureDecryption |= drmInitData.get(i).requiresSecureDecryption; } } MediaCodecInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType, requiresSecureDecryption); if (decoderInfo == null) { return requiresSecureDecryption && mediaCodecSelector.getDecoderInfo(mimeType, false) != null ? FORMAT_UNSUPPORTED_DRM : FORMAT_UNSUPPORTED_SUBTYPE; } if (!supportsFormatDrm(drmSessionManager, drmInitData)) { return FORMAT_UNSUPPORTED_DRM; } boolean decoderCapable = decoderInfo.isCodecSupported(format.codecs); if (decoderCapable && format.width > 0 && format.height > 0) { if (Util.SDK_INT >= 21) { decoderCapable = decoderInfo.isVideoSizeAndRateSupportedV21(format.width, format.height, format.frameRate); } else { decoderCapable = format.width * format.height <= MediaCodecUtil.maxH264DecodableFrameSize(); if (!decoderCapable) { Log.d(TAG, "FalseCheck [legacyFrameSize, " + format.width + "x" + format.height + "] [" + Util.DEVICE_DEBUG_INFO + "]"); } } } int adaptiveSupport = decoderInfo.adaptive ? ADAPTIVE_SEAMLESS : ADAPTIVE_NOT_SEAMLESS; int tunnelingSupport = decoderInfo.tunneling ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED; int formatSupport = decoderCapable ? FORMAT_HANDLED : FORMAT_EXCEEDS_CAPABILITIES; return adaptiveSupport | tunnelingSupport | formatSupport; }
private void setSurface(Surface surface) throws ExoPlaybackException { if (surface == null) { // Use a dummy surface if possible. if (dummySurface != null) { surface = dummySurface; } else { MediaCodecInfo codecInfo = getCodecInfo(); if (codecInfo != null && shouldUseDummySurface(codecInfo.secure)) { dummySurface = DummySurface.newInstanceV17(context, codecInfo.secure); surface = dummySurface; } } } // We only need to update the codec if the surface has changed. if (this.surface != surface) { this.surface = surface; int state = getState(); if (state == STATE_ENABLED || state == STATE_STARTED) { MediaCodec codec = getCodec(); if (Util.SDK_INT >= 23 && codec != null && surface != null && !codecNeedsSetOutputSurfaceWorkaround) { setOutputSurfaceV23(codec, surface); } else { releaseCodec(); maybeInitCodec(); } } if (surface != null && surface != dummySurface) { // If we know the video size, report it again immediately. maybeRenotifyVideoSizeChanged(); // We haven't rendered to the new surface yet. clearRenderedFirstFrame(); if (state == STATE_STARTED) { setJoiningDeadlineMs(); } } else { // The surface has been removed. clearReportedVideoSize(); clearRenderedFirstFrame(); } } else if (surface != null && surface != dummySurface) { // The surface is set and unchanged. If we know the video size and/or have already rendered to // the surface, report these again immediately. maybeRenotifyVideoSizeChanged(); maybeRenotifyRenderedFirstFrame(); } }
@Override protected boolean shouldInitCodec(MediaCodecInfo codecInfo) { return surface != null || shouldUseDummySurface(codecInfo.secure); }