mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Automatically use DummySurface when possible
Issue: #677 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=157831796
This commit is contained in:
parent
32b5a80291
commit
ba9114c9c7
7 changed files with 167 additions and 77 deletions
|
|
@ -281,7 +281,7 @@ import java.util.Locale;
|
||||||
@Override
|
@Override
|
||||||
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
|
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
|
||||||
float pixelWidthHeightRatio) {
|
float pixelWidthHeightRatio) {
|
||||||
// Do nothing.
|
Log.d(TAG, "videoSizeChanged [" + width + ", " + height + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
||||||
|
|
||||||
if (outputMode == VpxDecoder.OUTPUT_MODE_NONE) {
|
if (outputMode == VpxDecoder.OUTPUT_MODE_NONE) {
|
||||||
// Skip frames in sync with playback, so we'll be at the right frame if the mode changes.
|
// Skip frames in sync with playback, so we'll be at the right frame if the mode changes.
|
||||||
if (outputBuffer.timeUs <= positionUs) {
|
if (isBufferLate(outputBuffer.timeUs - positionUs)) {
|
||||||
skipBuffer();
|
skipBuffer();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -280,7 +280,6 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the current frame should be dropped.
|
* Returns whether the current frame should be dropped.
|
||||||
*
|
*
|
||||||
|
|
@ -293,10 +292,8 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
||||||
*/
|
*/
|
||||||
protected boolean shouldDropOutputBuffer(long outputBufferTimeUs, long nextOutputBufferTimeUs,
|
protected boolean shouldDropOutputBuffer(long outputBufferTimeUs, long nextOutputBufferTimeUs,
|
||||||
long positionUs, long joiningDeadlineMs) {
|
long positionUs, long joiningDeadlineMs) {
|
||||||
// Drop the frame if we're joining and are more than 30ms late, or if we have the next frame
|
return isBufferLate(outputBufferTimeUs - positionUs)
|
||||||
// and that's also late. Else we'll render what we have.
|
&& (joiningDeadlineMs != C.TIME_UNSET || nextOutputBufferTimeUs != C.TIME_UNSET);
|
||||||
return (joiningDeadlineMs != C.TIME_UNSET && outputBufferTimeUs < positionUs - 30000)
|
|
||||||
|| (nextOutputBufferTimeUs != C.TIME_UNSET && nextOutputBufferTimeUs < positionUs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderBuffer() {
|
private void renderBuffer() {
|
||||||
|
|
@ -655,4 +652,9 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isBufferLate(long earlyUs) {
|
||||||
|
// Class a buffer as late if it should have been presented more than 30ms ago.
|
||||||
|
return earlyUs < -30000;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,14 @@ public final class MediaCodecInfo {
|
||||||
*/
|
*/
|
||||||
public final boolean tunneling;
|
public final boolean tunneling;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the decoder is secure.
|
||||||
|
*
|
||||||
|
* @see CodecCapabilities#isFeatureRequired(String)
|
||||||
|
* @see CodecCapabilities#FEATURE_SecurePlayback
|
||||||
|
*/
|
||||||
|
public final boolean secure;
|
||||||
|
|
||||||
private final String mimeType;
|
private final String mimeType;
|
||||||
private final CodecCapabilities capabilities;
|
private final CodecCapabilities capabilities;
|
||||||
|
|
||||||
|
|
@ -71,7 +79,7 @@ public final class MediaCodecInfo {
|
||||||
* @return The created instance.
|
* @return The created instance.
|
||||||
*/
|
*/
|
||||||
public static MediaCodecInfo newPassthroughInstance(String name) {
|
public static MediaCodecInfo newPassthroughInstance(String name) {
|
||||||
return new MediaCodecInfo(name, null, null, false);
|
return new MediaCodecInfo(name, null, null, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -84,7 +92,7 @@ public final class MediaCodecInfo {
|
||||||
*/
|
*/
|
||||||
public static MediaCodecInfo newInstance(String name, String mimeType,
|
public static MediaCodecInfo newInstance(String name, String mimeType,
|
||||||
CodecCapabilities capabilities) {
|
CodecCapabilities capabilities) {
|
||||||
return new MediaCodecInfo(name, mimeType, capabilities, false);
|
return new MediaCodecInfo(name, mimeType, capabilities, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -94,20 +102,22 @@ public final class MediaCodecInfo {
|
||||||
* @param mimeType A mime type supported by the {@link MediaCodec}.
|
* @param mimeType A mime type supported by the {@link MediaCodec}.
|
||||||
* @param capabilities The capabilities of the {@link MediaCodec} for the specified mime type.
|
* @param capabilities The capabilities of the {@link MediaCodec} for the specified mime type.
|
||||||
* @param forceDisableAdaptive Whether {@link #adaptive} should be forced to {@code false}.
|
* @param forceDisableAdaptive Whether {@link #adaptive} should be forced to {@code false}.
|
||||||
|
* @param forceSecure Whether {@link #secure} should be forced to {@code true}.
|
||||||
* @return The created instance.
|
* @return The created instance.
|
||||||
*/
|
*/
|
||||||
public static MediaCodecInfo newInstance(String name, String mimeType,
|
public static MediaCodecInfo newInstance(String name, String mimeType,
|
||||||
CodecCapabilities capabilities, boolean forceDisableAdaptive) {
|
CodecCapabilities capabilities, boolean forceDisableAdaptive, boolean forceSecure) {
|
||||||
return new MediaCodecInfo(name, mimeType, capabilities, forceDisableAdaptive);
|
return new MediaCodecInfo(name, mimeType, capabilities, forceDisableAdaptive, forceSecure);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MediaCodecInfo(String name, String mimeType, CodecCapabilities capabilities,
|
private MediaCodecInfo(String name, String mimeType, CodecCapabilities capabilities,
|
||||||
boolean forceDisableAdaptive) {
|
boolean forceDisableAdaptive, boolean forceSecure) {
|
||||||
this.name = Assertions.checkNotNull(name);
|
this.name = Assertions.checkNotNull(name);
|
||||||
this.mimeType = mimeType;
|
this.mimeType = mimeType;
|
||||||
this.capabilities = capabilities;
|
this.capabilities = capabilities;
|
||||||
adaptive = !forceDisableAdaptive && capabilities != null && isAdaptive(capabilities);
|
adaptive = !forceDisableAdaptive && capabilities != null && isAdaptive(capabilities);
|
||||||
tunneling = capabilities != null && isTunneling(capabilities);
|
tunneling = capabilities != null && isTunneling(capabilities);
|
||||||
|
secure = forceSecure || (capabilities != null && isSecure(capabilities));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -176,12 +186,12 @@ public final class MediaCodecInfo {
|
||||||
logNoSupport("sizeAndRate.vCaps");
|
logNoSupport("sizeAndRate.vCaps");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!areSizeAndRateSupported(videoCapabilities, width, height, frameRate)) {
|
if (!areSizeAndRateSupportedV21(videoCapabilities, width, height, frameRate)) {
|
||||||
// Capabilities are known to be inaccurately reported for vertical resolutions on some devices
|
// Capabilities are known to be inaccurately reported for vertical resolutions on some devices
|
||||||
// (b/31387661). If the video is vertical and the capabilities indicate support if the width
|
// (b/31387661). If the video is vertical and the capabilities indicate support if the width
|
||||||
// and height are swapped, we assume that the vertical resolution is also supported.
|
// and height are swapped, we assume that the vertical resolution is also supported.
|
||||||
if (width >= height
|
if (width >= height
|
||||||
|| !areSizeAndRateSupported(videoCapabilities, height, width, frameRate)) {
|
|| !areSizeAndRateSupportedV21(videoCapabilities, height, width, frameRate)) {
|
||||||
logNoSupport("sizeAndRate.support, " + width + "x" + height + "x" + frameRate);
|
logNoSupport("sizeAndRate.support, " + width + "x" + height + "x" + frameRate);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -290,14 +300,6 @@ public final class MediaCodecInfo {
|
||||||
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback);
|
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(21)
|
|
||||||
private static boolean areSizeAndRateSupported(VideoCapabilities capabilities, int width,
|
|
||||||
int height, double frameRate) {
|
|
||||||
return frameRate == Format.NO_VALUE || frameRate <= 0
|
|
||||||
? capabilities.isSizeSupported(width, height)
|
|
||||||
: capabilities.areSizeAndRateSupported(width, height, frameRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isTunneling(CodecCapabilities capabilities) {
|
private static boolean isTunneling(CodecCapabilities capabilities) {
|
||||||
return Util.SDK_INT >= 21 && isTunnelingV21(capabilities);
|
return Util.SDK_INT >= 21 && isTunnelingV21(capabilities);
|
||||||
}
|
}
|
||||||
|
|
@ -307,4 +309,21 @@ public final class MediaCodecInfo {
|
||||||
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_TunneledPlayback);
|
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_TunneledPlayback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isSecure(CodecCapabilities capabilities) {
|
||||||
|
return Util.SDK_INT >= 21 && isSecureV21(capabilities);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(21)
|
||||||
|
private static boolean isSecureV21(CodecCapabilities capabilities) {
|
||||||
|
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(21)
|
||||||
|
private static boolean areSizeAndRateSupportedV21(VideoCapabilities capabilities, int width,
|
||||||
|
int height, double frameRate) {
|
||||||
|
return frameRate == Format.NO_VALUE || frameRate <= 0
|
||||||
|
? capabilities.isSizeSupported(width, height)
|
||||||
|
: capabilities.areSizeAndRateSupported(width, height, frameRate);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -175,10 +175,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
private final MediaCodec.BufferInfo outputBufferInfo;
|
private final MediaCodec.BufferInfo outputBufferInfo;
|
||||||
|
|
||||||
private Format format;
|
private Format format;
|
||||||
private MediaCodec codec;
|
|
||||||
private DrmSession<FrameworkMediaCrypto> drmSession;
|
private DrmSession<FrameworkMediaCrypto> drmSession;
|
||||||
private DrmSession<FrameworkMediaCrypto> pendingDrmSession;
|
private DrmSession<FrameworkMediaCrypto> pendingDrmSession;
|
||||||
private boolean codecIsAdaptive;
|
private MediaCodec codec;
|
||||||
|
private MediaCodecInfo codecInfo;
|
||||||
private boolean codecNeedsDiscardToSpsWorkaround;
|
private boolean codecNeedsDiscardToSpsWorkaround;
|
||||||
private boolean codecNeedsFlushWorkaround;
|
private boolean codecNeedsFlushWorkaround;
|
||||||
private boolean codecNeedsAdaptationWorkaround;
|
private boolean codecNeedsAdaptationWorkaround;
|
||||||
|
|
@ -291,7 +291,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
protected final void maybeInitCodec() throws ExoPlaybackException {
|
protected final void maybeInitCodec() throws ExoPlaybackException {
|
||||||
if (!shouldInitCodec()) {
|
if (codec != null || format == null) {
|
||||||
|
// We have a codec already, or we don't have a format with which to instantiate one.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,18 +314,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaCodecInfo decoderInfo = null;
|
MediaCodecInfo codecInfo = null;
|
||||||
try {
|
try {
|
||||||
decoderInfo = getDecoderInfo(mediaCodecSelector, format, drmSessionRequiresSecureDecoder);
|
codecInfo = getDecoderInfo(mediaCodecSelector, format, drmSessionRequiresSecureDecoder);
|
||||||
if (decoderInfo == null && drmSessionRequiresSecureDecoder) {
|
if (codecInfo == null && drmSessionRequiresSecureDecoder) {
|
||||||
// The drm session indicates that a secure decoder is required, but the device does not have
|
// The drm session indicates that a secure decoder is required, but the device does not have
|
||||||
// one. Assuming that supportsFormat indicated support for the media being played, we know
|
// one. Assuming that supportsFormat indicated support for the media being played, we know
|
||||||
// that it does not require a secure output path. Most CDM implementations allow playback to
|
// that it does not require a secure output path. Most CDM implementations allow playback to
|
||||||
// proceed with a non-secure decoder in this case, so we try our luck.
|
// proceed with a non-secure decoder in this case, so we try our luck.
|
||||||
decoderInfo = getDecoderInfo(mediaCodecSelector, format, false);
|
codecInfo = getDecoderInfo(mediaCodecSelector, format, false);
|
||||||
if (decoderInfo != null) {
|
if (codecInfo != null) {
|
||||||
Log.w(TAG, "Drm session requires secure decoder for " + mimeType + ", but "
|
Log.w(TAG, "Drm session requires secure decoder for " + mimeType + ", but "
|
||||||
+ "no secure decoder available. Trying to proceed with " + decoderInfo.name + ".");
|
+ "no secure decoder available. Trying to proceed with " + codecInfo.name + ".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (DecoderQueryException e) {
|
} catch (DecoderQueryException e) {
|
||||||
|
|
@ -332,14 +333,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
drmSessionRequiresSecureDecoder, DecoderInitializationException.DECODER_QUERY_ERROR));
|
drmSessionRequiresSecureDecoder, DecoderInitializationException.DECODER_QUERY_ERROR));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decoderInfo == null) {
|
if (codecInfo == null) {
|
||||||
throwDecoderInitError(new DecoderInitializationException(format, null,
|
throwDecoderInitError(new DecoderInitializationException(format, null,
|
||||||
drmSessionRequiresSecureDecoder,
|
drmSessionRequiresSecureDecoder,
|
||||||
DecoderInitializationException.NO_SUITABLE_DECODER_ERROR));
|
DecoderInitializationException.NO_SUITABLE_DECODER_ERROR));
|
||||||
}
|
}
|
||||||
|
|
||||||
String codecName = decoderInfo.name;
|
if (!shouldInitCodec(codecInfo)) {
|
||||||
codecIsAdaptive = decoderInfo.adaptive;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.codecInfo = codecInfo;
|
||||||
|
String codecName = codecInfo.name;
|
||||||
codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format);
|
codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format);
|
||||||
codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName);
|
codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName);
|
||||||
codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName);
|
codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName);
|
||||||
|
|
@ -353,7 +358,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
codec = MediaCodec.createByCodecName(codecName);
|
codec = MediaCodec.createByCodecName(codecName);
|
||||||
TraceUtil.endSection();
|
TraceUtil.endSection();
|
||||||
TraceUtil.beginSection("configureCodec");
|
TraceUtil.beginSection("configureCodec");
|
||||||
configureCodec(decoderInfo, codec, format, mediaCrypto);
|
configureCodec(codecInfo, codec, format, mediaCrypto);
|
||||||
TraceUtil.endSection();
|
TraceUtil.endSection();
|
||||||
TraceUtil.beginSection("startCodec");
|
TraceUtil.beginSection("startCodec");
|
||||||
codec.start();
|
codec.start();
|
||||||
|
|
@ -380,14 +385,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
throw ExoPlaybackException.createForRenderer(e, getIndex());
|
throw ExoPlaybackException.createForRenderer(e, getIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean shouldInitCodec() {
|
protected boolean shouldInitCodec(MediaCodecInfo codecInfo) {
|
||||||
return codec == null && format != null;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final MediaCodec getCodec() {
|
protected final MediaCodec getCodec() {
|
||||||
return codec;
|
return codec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected final MediaCodecInfo getCodecInfo() {
|
||||||
|
return codecInfo;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onEnabled(boolean joining) throws ExoPlaybackException {
|
protected void onEnabled(boolean joining) throws ExoPlaybackException {
|
||||||
decoderCounters = new DecoderCounters();
|
decoderCounters = new DecoderCounters();
|
||||||
|
|
@ -426,31 +435,31 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void releaseCodec() {
|
protected void releaseCodec() {
|
||||||
|
codecHotswapDeadlineMs = C.TIME_UNSET;
|
||||||
|
inputIndex = C.INDEX_UNSET;
|
||||||
|
outputIndex = C.INDEX_UNSET;
|
||||||
|
waitingForKeys = false;
|
||||||
|
shouldSkipOutputBuffer = false;
|
||||||
|
decodeOnlyPresentationTimestamps.clear();
|
||||||
|
inputBuffers = null;
|
||||||
|
outputBuffers = null;
|
||||||
|
codecInfo = null;
|
||||||
|
codecReconfigured = false;
|
||||||
|
codecReceivedBuffers = false;
|
||||||
|
codecNeedsDiscardToSpsWorkaround = false;
|
||||||
|
codecNeedsFlushWorkaround = false;
|
||||||
|
codecNeedsAdaptationWorkaround = false;
|
||||||
|
codecNeedsEosPropagationWorkaround = false;
|
||||||
|
codecNeedsEosFlushWorkaround = false;
|
||||||
|
codecNeedsMonoChannelCountWorkaround = false;
|
||||||
|
codecNeedsAdaptationWorkaroundBuffer = false;
|
||||||
|
shouldSkipAdaptationWorkaroundOutputBuffer = false;
|
||||||
|
codecReceivedEos = false;
|
||||||
|
codecReconfigurationState = RECONFIGURATION_STATE_NONE;
|
||||||
|
codecReinitializationState = REINITIALIZATION_STATE_NONE;
|
||||||
|
buffer.data = null;
|
||||||
if (codec != null) {
|
if (codec != null) {
|
||||||
codecHotswapDeadlineMs = C.TIME_UNSET;
|
|
||||||
inputIndex = C.INDEX_UNSET;
|
|
||||||
outputIndex = C.INDEX_UNSET;
|
|
||||||
waitingForKeys = false;
|
|
||||||
shouldSkipOutputBuffer = false;
|
|
||||||
decodeOnlyPresentationTimestamps.clear();
|
|
||||||
inputBuffers = null;
|
|
||||||
outputBuffers = null;
|
|
||||||
codecReconfigured = false;
|
|
||||||
codecReceivedBuffers = false;
|
|
||||||
codecIsAdaptive = false;
|
|
||||||
codecNeedsDiscardToSpsWorkaround = false;
|
|
||||||
codecNeedsFlushWorkaround = false;
|
|
||||||
codecNeedsAdaptationWorkaround = false;
|
|
||||||
codecNeedsEosPropagationWorkaround = false;
|
|
||||||
codecNeedsEosFlushWorkaround = false;
|
|
||||||
codecNeedsMonoChannelCountWorkaround = false;
|
|
||||||
codecNeedsAdaptationWorkaroundBuffer = false;
|
|
||||||
shouldSkipAdaptationWorkaroundOutputBuffer = false;
|
|
||||||
codecReceivedEos = false;
|
|
||||||
codecReconfigurationState = RECONFIGURATION_STATE_NONE;
|
|
||||||
codecReinitializationState = REINITIALIZATION_STATE_NONE;
|
|
||||||
decoderCounters.decoderReleaseCount++;
|
decoderCounters.decoderReleaseCount++;
|
||||||
buffer.data = null;
|
|
||||||
try {
|
try {
|
||||||
codec.stop();
|
codec.stop();
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -781,7 +790,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pendingDrmSession == drmSession && codec != null
|
if (pendingDrmSession == drmSession && codec != null
|
||||||
&& canReconfigureCodec(codec, codecIsAdaptive, oldFormat, format)) {
|
&& canReconfigureCodec(codec, codecInfo.adaptive, oldFormat, format)) {
|
||||||
codecReconfigured = true;
|
codecReconfigured = true;
|
||||||
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
|
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
|
||||||
codecNeedsAdaptationWorkaroundBuffer = codecNeedsAdaptationWorkaround
|
codecNeedsAdaptationWorkaroundBuffer = codecNeedsAdaptationWorkaround
|
||||||
|
|
|
||||||
|
|
@ -230,10 +230,10 @@ public final class MediaCodecUtil {
|
||||||
if ((secureDecodersExplicit && key.secure == secure)
|
if ((secureDecodersExplicit && key.secure == secure)
|
||||||
|| (!secureDecodersExplicit && !key.secure)) {
|
|| (!secureDecodersExplicit && !key.secure)) {
|
||||||
decoderInfos.add(MediaCodecInfo.newInstance(codecName, mimeType, capabilities,
|
decoderInfos.add(MediaCodecInfo.newInstance(codecName, mimeType, capabilities,
|
||||||
forceDisableAdaptive));
|
forceDisableAdaptive, false));
|
||||||
} else if (!secureDecodersExplicit && secure) {
|
} else if (!secureDecodersExplicit && secure) {
|
||||||
decoderInfos.add(MediaCodecInfo.newInstance(codecName + ".secure", mimeType,
|
decoderInfos.add(MediaCodecInfo.newInstance(codecName + ".secure", mimeType,
|
||||||
capabilities, forceDisableAdaptive));
|
capabilities, forceDisableAdaptive, true));
|
||||||
// It only makes sense to have one synthesized secure decoder, return immediately.
|
// It only makes sense to have one synthesized secure decoder, return immediately.
|
||||||
return decoderInfos;
|
return decoderInfos;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -255,8 +255,8 @@ public final class DummySurface extends Surface {
|
||||||
if (secure) {
|
if (secure) {
|
||||||
glAttributes = new int[] {
|
glAttributes = new int[] {
|
||||||
EGL_CONTEXT_CLIENT_VERSION, 2,
|
EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||||
EGL_PROTECTED_CONTENT_EXT,
|
EGL_PROTECTED_CONTENT_EXT, EGL_TRUE,
|
||||||
EGL_TRUE, EGL_NONE};
|
EGL_NONE};
|
||||||
} else {
|
} else {
|
||||||
glAttributes = new int[] {
|
glAttributes = new int[] {
|
||||||
EGL_CONTEXT_CLIENT_VERSION, 2,
|
EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.TraceUtil;
|
import com.google.android.exoplayer2.util.TraceUtil;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
@ -77,6 +78,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
private CodecMaxValues codecMaxValues;
|
private CodecMaxValues codecMaxValues;
|
||||||
|
|
||||||
private Surface surface;
|
private Surface surface;
|
||||||
|
private Surface dummySurface;
|
||||||
@C.VideoScalingMode
|
@C.VideoScalingMode
|
||||||
private int scalingMode;
|
private int scalingMode;
|
||||||
private boolean renderedFirstFrame;
|
private boolean renderedFirstFrame;
|
||||||
|
|
@ -263,7 +265,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isReady() {
|
public boolean isReady() {
|
||||||
if ((renderedFirstFrame || super.shouldInitCodec()) && super.isReady()) {
|
if (super.isReady() && (renderedFirstFrame || (dummySurface != null && surface == dummySurface)
|
||||||
|
|| getCodec() == null)) {
|
||||||
// Ready. If we were joining then we've now joined, so clear the joining deadline.
|
// Ready. If we were joining then we've now joined, so clear the joining deadline.
|
||||||
joiningDeadlineMs = C.TIME_UNSET;
|
joiningDeadlineMs = C.TIME_UNSET;
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -306,6 +309,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
clearRenderedFirstFrame();
|
clearRenderedFirstFrame();
|
||||||
frameReleaseTimeHelper.disable();
|
frameReleaseTimeHelper.disable();
|
||||||
tunnelingOnFrameRenderedListener = null;
|
tunnelingOnFrameRenderedListener = null;
|
||||||
|
tunneling = false;
|
||||||
try {
|
try {
|
||||||
super.onDisabled();
|
super.onDisabled();
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -330,6 +334,18 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSurface(Surface surface) throws ExoPlaybackException {
|
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(codecInfo.secure);
|
||||||
|
surface = dummySurface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// We only need to update the codec if the surface has changed.
|
// We only need to update the codec if the surface has changed.
|
||||||
if (this.surface != surface) {
|
if (this.surface != surface) {
|
||||||
this.surface = surface;
|
this.surface = surface;
|
||||||
|
|
@ -343,7 +359,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
maybeInitCodec();
|
maybeInitCodec();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (surface != null) {
|
if (surface != null && surface != dummySurface) {
|
||||||
// If we know the video size, report it again immediately.
|
// If we know the video size, report it again immediately.
|
||||||
maybeRenotifyVideoSizeChanged();
|
maybeRenotifyVideoSizeChanged();
|
||||||
// We haven't rendered to the new surface yet.
|
// We haven't rendered to the new surface yet.
|
||||||
|
|
@ -356,17 +372,17 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
clearReportedVideoSize();
|
clearReportedVideoSize();
|
||||||
clearRenderedFirstFrame();
|
clearRenderedFirstFrame();
|
||||||
}
|
}
|
||||||
} else if (surface != null) {
|
} else if (surface != null && surface != dummySurface) {
|
||||||
// The surface is unchanged and non-null. If we know the video size and/or have already
|
// The surface is set and unchanged. If we know the video size and/or have already rendered to
|
||||||
// rendered to the surface, report these again immediately.
|
// the surface, report these again immediately.
|
||||||
maybeRenotifyVideoSizeChanged();
|
maybeRenotifyVideoSizeChanged();
|
||||||
maybeRenotifyRenderedFirstFrame();
|
maybeRenotifyRenderedFirstFrame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean shouldInitCodec() {
|
protected boolean shouldInitCodec(MediaCodecInfo codecInfo) {
|
||||||
return super.shouldInitCodec() && surface != null && surface.isValid();
|
return surface != null || shouldUseDummySurface(codecInfo.secure);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -375,12 +391,34 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
codecMaxValues = getCodecMaxValues(codecInfo, format, streamFormats);
|
codecMaxValues = getCodecMaxValues(codecInfo, format, streamFormats);
|
||||||
MediaFormat mediaFormat = getMediaFormat(format, codecMaxValues, deviceNeedsAutoFrcWorkaround,
|
MediaFormat mediaFormat = getMediaFormat(format, codecMaxValues, deviceNeedsAutoFrcWorkaround,
|
||||||
tunnelingAudioSessionId);
|
tunnelingAudioSessionId);
|
||||||
|
if (surface == null) {
|
||||||
|
Assertions.checkState(shouldUseDummySurface(codecInfo.secure));
|
||||||
|
if (dummySurface == null) {
|
||||||
|
dummySurface = DummySurface.newInstanceV17(codecInfo.secure);
|
||||||
|
}
|
||||||
|
surface = dummySurface;
|
||||||
|
}
|
||||||
codec.configure(mediaFormat, surface, crypto, 0);
|
codec.configure(mediaFormat, surface, crypto, 0);
|
||||||
if (Util.SDK_INT >= 23 && tunneling) {
|
if (Util.SDK_INT >= 23 && tunneling) {
|
||||||
tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codec);
|
tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void releaseCodec() {
|
||||||
|
try {
|
||||||
|
super.releaseCodec();
|
||||||
|
} finally {
|
||||||
|
if (dummySurface != null) {
|
||||||
|
if (surface == dummySurface) {
|
||||||
|
surface = null;
|
||||||
|
}
|
||||||
|
dummySurface.release();
|
||||||
|
dummySurface = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCodecInitialized(String name, long initializedTimestampMs,
|
protected void onCodecInitialized(String name, long initializedTimestampMs,
|
||||||
long initializationDurationMs) {
|
long initializationDurationMs) {
|
||||||
|
|
@ -452,11 +490,22 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
pendingOutputStreamOffsetCount);
|
pendingOutputStreamOffsetCount);
|
||||||
}
|
}
|
||||||
long presentationTimeUs = bufferPresentationTimeUs - outputStreamOffsetUs;
|
long presentationTimeUs = bufferPresentationTimeUs - outputStreamOffsetUs;
|
||||||
|
|
||||||
if (shouldSkip) {
|
if (shouldSkip) {
|
||||||
skipOutputBuffer(codec, bufferIndex, presentationTimeUs);
|
skipOutputBuffer(codec, bufferIndex, presentationTimeUs);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long earlyUs = bufferPresentationTimeUs - positionUs;
|
||||||
|
if (surface == dummySurface) {
|
||||||
|
// Skip frames in sync with playback, so we'll be at the right frame if the mode changes.
|
||||||
|
if (isBufferLate(earlyUs)) {
|
||||||
|
skipOutputBuffer(codec, bufferIndex, presentationTimeUs);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!renderedFirstFrame) {
|
if (!renderedFirstFrame) {
|
||||||
if (Util.SDK_INT >= 21) {
|
if (Util.SDK_INT >= 21) {
|
||||||
renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, System.nanoTime());
|
renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, System.nanoTime());
|
||||||
|
|
@ -470,9 +519,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute how many microseconds it is until the buffer's presentation time.
|
// Fine-grained adjustment of earlyUs based on the elapsed time since the start of the current
|
||||||
|
// iteration of the rendering loop.
|
||||||
long elapsedSinceStartOfLoopUs = (SystemClock.elapsedRealtime() * 1000) - elapsedRealtimeUs;
|
long elapsedSinceStartOfLoopUs = (SystemClock.elapsedRealtime() * 1000) - elapsedRealtimeUs;
|
||||||
long earlyUs = bufferPresentationTimeUs - positionUs - elapsedSinceStartOfLoopUs;
|
earlyUs -= elapsedSinceStartOfLoopUs;
|
||||||
|
|
||||||
// Compute the buffer's desired release time in nanoseconds.
|
// Compute the buffer's desired release time in nanoseconds.
|
||||||
long systemTimeNs = System.nanoTime();
|
long systemTimeNs = System.nanoTime();
|
||||||
|
|
@ -484,7 +534,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000;
|
earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000;
|
||||||
|
|
||||||
if (shouldDropOutputBuffer(earlyUs, elapsedRealtimeUs)) {
|
if (shouldDropOutputBuffer(earlyUs, elapsedRealtimeUs)) {
|
||||||
// We're more than 30ms late rendering the frame.
|
|
||||||
dropOutputBuffer(codec, bufferIndex, presentationTimeUs);
|
dropOutputBuffer(codec, bufferIndex, presentationTimeUs);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -526,8 +575,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
* measured at the start of the current iteration of the rendering loop.
|
* measured at the start of the current iteration of the rendering loop.
|
||||||
*/
|
*/
|
||||||
protected boolean shouldDropOutputBuffer(long earlyUs, long elapsedRealtimeUs) {
|
protected boolean shouldDropOutputBuffer(long earlyUs, long elapsedRealtimeUs) {
|
||||||
// Drop the frame if we're more than 30ms late rendering the frame.
|
return isBufferLate(earlyUs);
|
||||||
return earlyUs < -30000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -604,6 +652,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
maybeNotifyRenderedFirstFrame();
|
maybeNotifyRenderedFirstFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean shouldUseDummySurface(boolean codecIsSecure) {
|
||||||
|
// TODO: Work out when we can safely uncomment the secure case below. This case is currently
|
||||||
|
// broken on Galaxy S8 [Internal: b/37197802].
|
||||||
|
return Util.SDK_INT >= 23 && !tunneling
|
||||||
|
&& (!codecIsSecure /* || DummySurface.SECURE_SUPPORTED */);
|
||||||
|
}
|
||||||
|
|
||||||
private void setJoiningDeadlineMs() {
|
private void setJoiningDeadlineMs() {
|
||||||
joiningDeadlineMs = allowedJoiningTimeMs > 0
|
joiningDeadlineMs = allowedJoiningTimeMs > 0
|
||||||
? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET;
|
? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET;
|
||||||
|
|
@ -674,6 +729,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isBufferLate(long earlyUs) {
|
||||||
|
// Class a buffer as late if it should have been presented more than 30ms ago.
|
||||||
|
return earlyUs < -30000;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
private static MediaFormat getMediaFormat(Format format, CodecMaxValues codecMaxValues,
|
private static MediaFormat getMediaFormat(Format format, CodecMaxValues codecMaxValues,
|
||||||
boolean deviceNeedsAutoFrcWorkaround, int tunnelingAudioSessionId) {
|
boolean deviceNeedsAutoFrcWorkaround, int tunnelingAudioSessionId) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue