Automatically use DummySurface when possible

Issue: #677

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=157831796
This commit is contained in:
olly 2017-06-02 08:10:58 -07:00 committed by Oliver Woodman
parent 32b5a80291
commit ba9114c9c7
7 changed files with 167 additions and 77 deletions

View file

@ -281,7 +281,7 @@ import java.util.Locale;
@Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
float pixelWidthHeightRatio) {
// Do nothing.
Log.d(TAG, "videoSizeChanged [" + width + ", " + height + "]");
}
@Override

View file

@ -255,7 +255,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
if (outputMode == VpxDecoder.OUTPUT_MODE_NONE) {
// 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();
return true;
}
@ -280,7 +280,6 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
return false;
}
/**
* 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,
long positionUs, long joiningDeadlineMs) {
// Drop the frame if we're joining and are more than 30ms late, or if we have the next frame
// and that's also late. Else we'll render what we have.
return (joiningDeadlineMs != C.TIME_UNSET && outputBufferTimeUs < positionUs - 30000)
|| (nextOutputBufferTimeUs != C.TIME_UNSET && nextOutputBufferTimeUs < positionUs);
return isBufferLate(outputBufferTimeUs - positionUs)
&& (joiningDeadlineMs != C.TIME_UNSET || nextOutputBufferTimeUs != C.TIME_UNSET);
}
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;
}
}

View file

@ -61,6 +61,14 @@ public final class MediaCodecInfo {
*/
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 CodecCapabilities capabilities;
@ -71,7 +79,7 @@ public final class MediaCodecInfo {
* @return The created instance.
*/
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,
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 capabilities The capabilities of the {@link MediaCodec} for the specified mime type.
* @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.
*/
public static MediaCodecInfo newInstance(String name, String mimeType,
CodecCapabilities capabilities, boolean forceDisableAdaptive) {
return new MediaCodecInfo(name, mimeType, capabilities, forceDisableAdaptive);
CodecCapabilities capabilities, boolean forceDisableAdaptive, boolean forceSecure) {
return new MediaCodecInfo(name, mimeType, capabilities, forceDisableAdaptive, forceSecure);
}
private MediaCodecInfo(String name, String mimeType, CodecCapabilities capabilities,
boolean forceDisableAdaptive) {
boolean forceDisableAdaptive, boolean forceSecure) {
this.name = Assertions.checkNotNull(name);
this.mimeType = mimeType;
this.capabilities = capabilities;
adaptive = !forceDisableAdaptive && capabilities != null && isAdaptive(capabilities);
tunneling = capabilities != null && isTunneling(capabilities);
secure = forceSecure || (capabilities != null && isSecure(capabilities));
}
/**
@ -176,12 +186,12 @@ public final class MediaCodecInfo {
logNoSupport("sizeAndRate.vCaps");
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
// (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.
if (width >= height
|| !areSizeAndRateSupported(videoCapabilities, height, width, frameRate)) {
|| !areSizeAndRateSupportedV21(videoCapabilities, height, width, frameRate)) {
logNoSupport("sizeAndRate.support, " + width + "x" + height + "x" + frameRate);
return false;
}
@ -290,14 +300,6 @@ public final class MediaCodecInfo {
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) {
return Util.SDK_INT >= 21 && isTunnelingV21(capabilities);
}
@ -307,4 +309,21 @@ public final class MediaCodecInfo {
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);
}
}

View file

@ -175,10 +175,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private final MediaCodec.BufferInfo outputBufferInfo;
private Format format;
private MediaCodec codec;
private DrmSession<FrameworkMediaCrypto> drmSession;
private DrmSession<FrameworkMediaCrypto> pendingDrmSession;
private boolean codecIsAdaptive;
private MediaCodec codec;
private MediaCodecInfo codecInfo;
private boolean codecNeedsDiscardToSpsWorkaround;
private boolean codecNeedsFlushWorkaround;
private boolean codecNeedsAdaptationWorkaround;
@ -291,7 +291,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@SuppressWarnings("deprecation")
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;
}
@ -313,18 +314,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
}
}
MediaCodecInfo decoderInfo = null;
MediaCodecInfo codecInfo = null;
try {
decoderInfo = getDecoderInfo(mediaCodecSelector, format, drmSessionRequiresSecureDecoder);
if (decoderInfo == null && drmSessionRequiresSecureDecoder) {
codecInfo = getDecoderInfo(mediaCodecSelector, format, drmSessionRequiresSecureDecoder);
if (codecInfo == null && drmSessionRequiresSecureDecoder) {
// 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
// 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.
decoderInfo = getDecoderInfo(mediaCodecSelector, format, false);
if (decoderInfo != null) {
codecInfo = getDecoderInfo(mediaCodecSelector, format, false);
if (codecInfo != null) {
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) {
@ -332,14 +333,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
drmSessionRequiresSecureDecoder, DecoderInitializationException.DECODER_QUERY_ERROR));
}
if (decoderInfo == null) {
if (codecInfo == null) {
throwDecoderInitError(new DecoderInitializationException(format, null,
drmSessionRequiresSecureDecoder,
DecoderInitializationException.NO_SUITABLE_DECODER_ERROR));
}
String codecName = decoderInfo.name;
codecIsAdaptive = decoderInfo.adaptive;
if (!shouldInitCodec(codecInfo)) {
return;
}
this.codecInfo = codecInfo;
String codecName = codecInfo.name;
codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format);
codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName);
codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName);
@ -353,7 +358,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codec = MediaCodec.createByCodecName(codecName);
TraceUtil.endSection();
TraceUtil.beginSection("configureCodec");
configureCodec(decoderInfo, codec, format, mediaCrypto);
configureCodec(codecInfo, codec, format, mediaCrypto);
TraceUtil.endSection();
TraceUtil.beginSection("startCodec");
codec.start();
@ -380,14 +385,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
protected boolean shouldInitCodec() {
return codec == null && format != null;
protected boolean shouldInitCodec(MediaCodecInfo codecInfo) {
return true;
}
protected final MediaCodec getCodec() {
return codec;
}
protected final MediaCodecInfo getCodecInfo() {
return codecInfo;
}
@Override
protected void onEnabled(boolean joining) throws ExoPlaybackException {
decoderCounters = new DecoderCounters();
@ -426,31 +435,31 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
}
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) {
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++;
buffer.data = null;
try {
codec.stop();
} finally {
@ -781,7 +790,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
}
if (pendingDrmSession == drmSession && codec != null
&& canReconfigureCodec(codec, codecIsAdaptive, oldFormat, format)) {
&& canReconfigureCodec(codec, codecInfo.adaptive, oldFormat, format)) {
codecReconfigured = true;
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
codecNeedsAdaptationWorkaroundBuffer = codecNeedsAdaptationWorkaround

View file

@ -230,10 +230,10 @@ public final class MediaCodecUtil {
if ((secureDecodersExplicit && key.secure == secure)
|| (!secureDecodersExplicit && !key.secure)) {
decoderInfos.add(MediaCodecInfo.newInstance(codecName, mimeType, capabilities,
forceDisableAdaptive));
forceDisableAdaptive, false));
} else if (!secureDecodersExplicit && secure) {
decoderInfos.add(MediaCodecInfo.newInstance(codecName + ".secure", mimeType,
capabilities, forceDisableAdaptive));
capabilities, forceDisableAdaptive, true));
// It only makes sense to have one synthesized secure decoder, return immediately.
return decoderInfos;
}

View file

@ -255,8 +255,8 @@ public final class DummySurface extends Surface {
if (secure) {
glAttributes = new int[] {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_PROTECTED_CONTENT_EXT,
EGL_TRUE, EGL_NONE};
EGL_PROTECTED_CONTENT_EXT, EGL_TRUE,
EGL_NONE};
} else {
glAttributes = new int[] {
EGL_CONTEXT_CLIENT_VERSION, 2,

View file

@ -40,6 +40,7 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
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.TraceUtil;
import com.google.android.exoplayer2.util.Util;
@ -77,6 +78,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
private CodecMaxValues codecMaxValues;
private Surface surface;
private Surface dummySurface;
@C.VideoScalingMode
private int scalingMode;
private boolean renderedFirstFrame;
@ -263,7 +265,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
@Override
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.
joiningDeadlineMs = C.TIME_UNSET;
return true;
@ -306,6 +309,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
clearRenderedFirstFrame();
frameReleaseTimeHelper.disable();
tunnelingOnFrameRenderedListener = null;
tunneling = false;
try {
super.onDisabled();
} finally {
@ -330,6 +334,18 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
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.
if (this.surface != surface) {
this.surface = surface;
@ -343,7 +359,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
maybeInitCodec();
}
}
if (surface != null) {
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.
@ -356,17 +372,17 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
clearReportedVideoSize();
clearRenderedFirstFrame();
}
} else if (surface != null) {
// The surface is unchanged and non-null. If we know the video size and/or have already
// rendered to the surface, report these again immediately.
} 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() {
return super.shouldInitCodec() && surface != null && surface.isValid();
protected boolean shouldInitCodec(MediaCodecInfo codecInfo) {
return surface != null || shouldUseDummySurface(codecInfo.secure);
}
@Override
@ -375,12 +391,34 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
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(codecInfo.secure);
}
surface = dummySurface;
}
codec.configure(mediaFormat, surface, crypto, 0);
if (Util.SDK_INT >= 23 && tunneling) {
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
protected void onCodecInitialized(String name, long initializedTimestampMs,
long initializationDurationMs) {
@ -452,11 +490,22 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
pendingOutputStreamOffsetCount);
}
long presentationTimeUs = bufferPresentationTimeUs - outputStreamOffsetUs;
if (shouldSkip) {
skipOutputBuffer(codec, bufferIndex, presentationTimeUs);
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 (Util.SDK_INT >= 21) {
renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, System.nanoTime());
@ -470,9 +519,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
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 earlyUs = bufferPresentationTimeUs - positionUs - elapsedSinceStartOfLoopUs;
earlyUs -= elapsedSinceStartOfLoopUs;
// Compute the buffer's desired release time in nanoseconds.
long systemTimeNs = System.nanoTime();
@ -484,7 +534,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000;
if (shouldDropOutputBuffer(earlyUs, elapsedRealtimeUs)) {
// We're more than 30ms late rendering the frame.
dropOutputBuffer(codec, bufferIndex, presentationTimeUs);
return true;
}
@ -526,8 +575,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
* measured at the start of the current iteration of the rendering loop.
*/
protected boolean shouldDropOutputBuffer(long earlyUs, long elapsedRealtimeUs) {
// Drop the frame if we're more than 30ms late rendering the frame.
return earlyUs < -30000;
return isBufferLate(earlyUs);
}
/**
@ -604,6 +652,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
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() {
joiningDeadlineMs = allowedJoiningTimeMs > 0
? (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")
private static MediaFormat getMediaFormat(Format format, CodecMaxValues codecMaxValues,
boolean deviceNeedsAutoFrcWorkaround, int tunnelingAudioSessionId) {