From 028f3e6438be5c9f2a44c259e159600b9c8c6abb Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 27 Sep 2018 10:02:21 -0700 Subject: [PATCH] Don't recreate the audio decoder if Format is unchanged More specifically, if the parts of the Format that are used for decoder configuration are unchanged. Issue: #2826 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214791234 --- .../audio/MediaCodecAudioRenderer.java | 42 +++++-- .../mediacodec/MediaCodecRenderer.java | 105 ++++++++++++------ 2 files changed, 103 insertions(+), 44 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index 624e698ad6..89d2ee16b2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -346,7 +346,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media codecMaxInputSize = getCodecMaxInputSize(codecInfo, format, getStreamFormats()); codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name); passthroughEnabled = codecInfo.passthrough; - String codecMimeType = codecInfo.mimeType == null ? MimeTypes.AUDIO_RAW : codecInfo.mimeType; + String codecMimeType = passthroughEnabled ? MimeTypes.AUDIO_RAW : codecInfo.mimeType; MediaFormat mediaFormat = getMediaFormat(format, codecMimeType, codecMaxInputSize, codecOperatingRate); codec.configure(mediaFormat, /* surface= */ null, crypto, /* flags= */ 0); @@ -362,14 +362,22 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media @Override protected @KeepCodecResult int canKeepCodec( MediaCodec codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) { - if (getCodecMaxInputSize(codecInfo, newFormat) <= codecMaxInputSize - && codecInfo.isSeamlessAdaptationSupported( - oldFormat, newFormat, /* isNewFormatComplete= */ true) - && oldFormat.encoderDelay == 0 - && oldFormat.encoderPadding == 0 - && newFormat.encoderDelay == 0 - && newFormat.encoderPadding == 0) { + // 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; } @@ -720,6 +728,24 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media return format.maxInputSize; } + /** + * Returns whether two {@link Format}s will cause the same codec to be configured in an identical + * way, excluding {@link MediaFormat#KEY_MAX_INPUT_SIZE} and configuration that does not come from + * the {@link Format}. + * + * @param oldFormat The first format. + * @param newFormat The second format. + * @return Whether the two formats will cause a codec to be configured in an identical way, + * excluding {@link MediaFormat#KEY_MAX_INPUT_SIZE} and configuration that does not come from + * the {@link Format}. + */ + protected boolean areCodecConfigurationCompatible(Format oldFormat, Format newFormat) { + return Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType) + && oldFormat.channelCount == newFormat.channelCount + && oldFormat.sampleRate == newFormat.sampleRate + && oldFormat.initializationDataEquals(newFormat); + } + /** * Returns the framework {@link MediaFormat} that can be used to configure a {@link MediaCodec} * for decoding the given {@link Format} for playback. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 7e933e9474..bd77f32f5b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -185,19 +185,22 @@ public abstract class MediaCodecRenderer extends BaseRenderer { @Retention(RetentionPolicy.SOURCE) @IntDef({ KEEP_CODEC_RESULT_NO, - KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION, - KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION + KEEP_CODEC_RESULT_YES_WITH_FLUSH, + KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION, + KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION }) protected @interface KeepCodecResult {} /** The codec cannot be kept. */ protected static final int KEEP_CODEC_RESULT_NO = 0; - /** The codec can be kept. No reconfiguration is required. */ - protected static final int KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION = 1; + /** The codec can be kept, but must be flushed. */ + protected static final int KEEP_CODEC_RESULT_YES_WITH_FLUSH = 1; /** - * The codec can be kept, but must be reconfigured by prefixing the next input buffer with the new - * format's configuration data. + * The codec can be kept. It does not need to be flushed, but must be reconfigured by prefixing + * the next input buffer with the new format's configuration data. */ - protected static final int KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION = 3; + protected static final int KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION = 2; + /** The codec can be kept. It does not need to be flushed and no reconfiguration is required. */ + protected static final int KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION = 3; @Retention(RetentionPolicy.SOURCE) @IntDef({RECONFIGURATION_STATE_NONE, RECONFIGURATION_STATE_WRITE_PENDING, @@ -309,6 +312,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private boolean codecReconfigured; private @ReconfigurationState int codecReconfigurationState; private @ReinitializationState int codecReinitializationState; + private boolean codecReinitializationIsRelease; private boolean codecReceivedBuffers; private boolean codecReceivedEos; @@ -587,6 +591,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { codecReceivedEos = false; codecReconfigurationState = RECONFIGURATION_STATE_NONE; codecReinitializationState = REINITIALIZATION_STATE_NONE; + codecReinitializationIsRelease = false; codecConfiguredWithOperatingRate = false; if (codec != null) { decoderCounters.decoderReleaseCount++; @@ -682,10 +687,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer { releaseCodec(); maybeInitCodec(); } else if (codecReinitializationState != REINITIALIZATION_STATE_NONE) { - // We're already waiting to release and re-initialize the codec. Since we're now flushing, - // there's no need to wait any longer. - releaseCodec(); - maybeInitCodec(); + // We're already waiting to re-initialize the codec. Since we're now flushing, there's no need + // to wait any longer. + if (codecReinitializationIsRelease) { + releaseCodec(); + maybeInitCodec(); + } else { + codec.flush(); + codecReceivedBuffers = false; + codecReinitializationState = REINITIALIZATION_STATE_NONE; + } } else { // We can flush and re-use the existing decoder. codec.flush(); @@ -865,7 +876,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private boolean feedInputBuffer() throws ExoPlaybackException { if (codec == null || codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM || inputStreamEnded) { - // We need to reinitialize the codec or the input stream has ended. + // We need to re-initialize the codec or the input stream has ended. return false; } @@ -1043,7 +1054,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { * Called when a new format is read from the upstream {@link MediaPeriod}. * * @param newFormat The new format. - * @throws ExoPlaybackException If an error occurs reinitializing the {@link MediaCodec}. + * @throws ExoPlaybackException If an error occurs re-initializing the {@link MediaCodec}. */ protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { Format oldFormat = format; @@ -1067,18 +1078,28 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } } - boolean keepingCodec = false; - if (pendingDrmSession == drmSession && codec != null) { + if (codec == null) { + maybeInitCodec(); + return; + } + + // We have an existing codec that we may need to reconfigure or re-initialize. If the existing + // codec instance is being kept then its operating rate may need to be updated. + if (pendingDrmSession != drmSession) { + reinitializeCodec(/* release= */ true); + } else { switch (canKeepCodec(codec, codecInfo, oldFormat, format)) { case KEEP_CODEC_RESULT_NO: - // Do nothing. + reinitializeCodec(/* release= */ true); break; - case KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION: - keepingCodec = true; + case KEEP_CODEC_RESULT_YES_WITH_FLUSH: + reinitializeCodec(/* release= */ false); + updateCodecOperatingRate(); break; case KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION: - if (!codecNeedsReconfigureWorkaround) { - keepingCodec = true; + if (codecNeedsReconfigureWorkaround) { + reinitializeCodec(/* release= */ true); + } else { codecReconfigured = true; codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; codecNeedsAdaptationWorkaroundBuffer = @@ -1086,18 +1107,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer { || (codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION && format.width == oldFormat.width && format.height == oldFormat.height); + updateCodecOperatingRate(); } break; + case KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION: + updateCodecOperatingRate(); + break; default: throw new IllegalStateException(); // Never happens. } } - - if (!keepingCodec) { - reinitializeCodec(); - } else { - updateCodecOperatingRate(); - } } /** @@ -1211,13 +1230,15 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } this.codecOperatingRate = codecOperatingRate; - if (codec == null || codecReinitializationState != REINITIALIZATION_STATE_NONE) { - // Either no codec, or it's about to be reinitialized anyway. + if (codec == null + || (codecReinitializationState != REINITIALIZATION_STATE_NONE + && codecReinitializationIsRelease)) { + // Either no codec, or it's about to be released due to re-initialization anyway. } else if (codecOperatingRate == CODEC_OPERATING_RATE_UNSET && codecConfiguredWithOperatingRate) { // We need to clear the operating rate. The only way to do so is to instantiate a new codec // instance. See [Internal ref: b/71987865]. - reinitializeCodec(); + reinitializeCodec(/* release= */ true); } else if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET && (codecConfiguredWithOperatingRate || codecOperatingRate > assumedMinimumCodecOperatingRate)) { @@ -1231,18 +1252,26 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } /** - * Starts the process of releasing the existing codec and initializing a new one. This may occur - * immediately, or be deferred until any final output buffers have been dequeued. + * Starts the process of re-initializing the codec. This may occur immediately, or be deferred + * until any final output buffers have been dequeued. * + * @param release Whether re-initialization requires fully releasing the codec and instantiating a + * new instance, as opposed to flushing and reusing the current instance. * @throws ExoPlaybackException If an error occurs releasing or initializing a codec. */ - private void reinitializeCodec() throws ExoPlaybackException { + private void reinitializeCodec(boolean release) throws ExoPlaybackException { availableCodecInfos = null; if (codecReceivedBuffers) { // Signal end of stream and wait for any final output buffers before re-initialization. codecReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM; - } else { - // There aren't any final output buffers, so perform re-initialization immediately. + codecReinitializationIsRelease = release; + return; + } + + // Nothing has been queued to the decoder. If we need to fully release the codec and instantiate + // a new instance, do so immediately. If only a flush is required then we can do nothing, since + // flushing will be a no-op. + if (release) { releaseCodec(); maybeInitCodec(); } @@ -1449,8 +1478,12 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private void processEndOfStream() throws ExoPlaybackException { if (codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) { // We're waiting to re-initialize the codec, and have now processed all final buffers. - releaseCodec(); - maybeInitCodec(); + if (codecReinitializationIsRelease) { + releaseCodec(); + maybeInitCodec(); + } else { + flushCodec(); + } } else { outputStreamEnded = true; renderToEndOfStream();