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
This commit is contained in:
olly 2018-09-27 10:02:21 -07:00 committed by Oliver Woodman
parent 1dd0533af6
commit 028f3e6438
2 changed files with 103 additions and 44 deletions

View File

@ -346,7 +346,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
codecMaxInputSize = getCodecMaxInputSize(codecInfo, format, getStreamFormats()); codecMaxInputSize = getCodecMaxInputSize(codecInfo, format, getStreamFormats());
codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name); codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name);
passthroughEnabled = codecInfo.passthrough; passthroughEnabled = codecInfo.passthrough;
String codecMimeType = codecInfo.mimeType == null ? MimeTypes.AUDIO_RAW : codecInfo.mimeType; String codecMimeType = passthroughEnabled ? MimeTypes.AUDIO_RAW : codecInfo.mimeType;
MediaFormat mediaFormat = MediaFormat mediaFormat =
getMediaFormat(format, codecMimeType, codecMaxInputSize, codecOperatingRate); getMediaFormat(format, codecMimeType, codecMaxInputSize, codecOperatingRate);
codec.configure(mediaFormat, /* surface= */ null, crypto, /* flags= */ 0); codec.configure(mediaFormat, /* surface= */ null, crypto, /* flags= */ 0);
@ -362,14 +362,22 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@Override @Override
protected @KeepCodecResult int canKeepCodec( protected @KeepCodecResult int canKeepCodec(
MediaCodec codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) { MediaCodec codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
if (getCodecMaxInputSize(codecInfo, newFormat) <= codecMaxInputSize // TODO: We currently rely on recreating the codec when encoder delay or padding is non-zero.
&& codecInfo.isSeamlessAdaptationSupported( // Re-creating the codec is necessary to guarantee that onOutputFormatChanged is called, which
oldFormat, newFormat, /* isNewFormatComplete= */ true) // is where encoder delay and padding are propagated to the sink. We should find a better way to
&& oldFormat.encoderDelay == 0 // propagate these values, and then allow the codec to be re-used in cases where this would
&& oldFormat.encoderPadding == 0 // otherwise be possible.
&& newFormat.encoderDelay == 0 if (getCodecMaxInputSize(codecInfo, newFormat) > codecMaxInputSize
&& newFormat.encoderPadding == 0) { || 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; return KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION;
} else if (areCodecConfigurationCompatible(oldFormat, newFormat)) {
return KEEP_CODEC_RESULT_YES_WITH_FLUSH;
} else { } else {
return KEEP_CODEC_RESULT_NO; return KEEP_CODEC_RESULT_NO;
} }
@ -720,6 +728,24 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
return format.maxInputSize; 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} * Returns the framework {@link MediaFormat} that can be used to configure a {@link MediaCodec}
* for decoding the given {@link Format} for playback. * for decoding the given {@link Format} for playback.

View File

@ -185,19 +185,22 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({ @IntDef({
KEEP_CODEC_RESULT_NO, KEEP_CODEC_RESULT_NO,
KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION, KEEP_CODEC_RESULT_YES_WITH_FLUSH,
KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION,
KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION
}) })
protected @interface KeepCodecResult {} protected @interface KeepCodecResult {}
/** The codec cannot be kept. */ /** The codec cannot be kept. */
protected static final int KEEP_CODEC_RESULT_NO = 0; protected static final int KEEP_CODEC_RESULT_NO = 0;
/** The codec can be kept. No reconfiguration is required. */ /** The codec can be kept, but must be flushed. */
protected static final int KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION = 1; 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 * The codec can be kept. It does not need to be flushed, but must be reconfigured by prefixing
* format's configuration data. * 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) @Retention(RetentionPolicy.SOURCE)
@IntDef({RECONFIGURATION_STATE_NONE, RECONFIGURATION_STATE_WRITE_PENDING, @IntDef({RECONFIGURATION_STATE_NONE, RECONFIGURATION_STATE_WRITE_PENDING,
@ -309,6 +312,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private boolean codecReconfigured; private boolean codecReconfigured;
private @ReconfigurationState int codecReconfigurationState; private @ReconfigurationState int codecReconfigurationState;
private @ReinitializationState int codecReinitializationState; private @ReinitializationState int codecReinitializationState;
private boolean codecReinitializationIsRelease;
private boolean codecReceivedBuffers; private boolean codecReceivedBuffers;
private boolean codecReceivedEos; private boolean codecReceivedEos;
@ -587,6 +591,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecReceivedEos = false; codecReceivedEos = false;
codecReconfigurationState = RECONFIGURATION_STATE_NONE; codecReconfigurationState = RECONFIGURATION_STATE_NONE;
codecReinitializationState = REINITIALIZATION_STATE_NONE; codecReinitializationState = REINITIALIZATION_STATE_NONE;
codecReinitializationIsRelease = false;
codecConfiguredWithOperatingRate = false; codecConfiguredWithOperatingRate = false;
if (codec != null) { if (codec != null) {
decoderCounters.decoderReleaseCount++; decoderCounters.decoderReleaseCount++;
@ -682,10 +687,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
releaseCodec(); releaseCodec();
maybeInitCodec(); maybeInitCodec();
} else if (codecReinitializationState != REINITIALIZATION_STATE_NONE) { } else if (codecReinitializationState != REINITIALIZATION_STATE_NONE) {
// We're already waiting to release and re-initialize the codec. Since we're now flushing, // We're already waiting to re-initialize the codec. Since we're now flushing, there's no need
// there's no need to wait any longer. // to wait any longer.
releaseCodec(); if (codecReinitializationIsRelease) {
maybeInitCodec(); releaseCodec();
maybeInitCodec();
} else {
codec.flush();
codecReceivedBuffers = false;
codecReinitializationState = REINITIALIZATION_STATE_NONE;
}
} else { } else {
// We can flush and re-use the existing decoder. // We can flush and re-use the existing decoder.
codec.flush(); codec.flush();
@ -865,7 +876,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private boolean feedInputBuffer() throws ExoPlaybackException { private boolean feedInputBuffer() throws ExoPlaybackException {
if (codec == null || codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM if (codec == null || codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM
|| inputStreamEnded) { || 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; return false;
} }
@ -1043,7 +1054,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* Called when a new format is read from the upstream {@link MediaPeriod}. * Called when a new format is read from the upstream {@link MediaPeriod}.
* *
* @param newFormat The new format. * @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 { protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException {
Format oldFormat = format; Format oldFormat = format;
@ -1067,18 +1078,28 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
} }
boolean keepingCodec = false; if (codec == null) {
if (pendingDrmSession == drmSession && 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)) { switch (canKeepCodec(codec, codecInfo, oldFormat, format)) {
case KEEP_CODEC_RESULT_NO: case KEEP_CODEC_RESULT_NO:
// Do nothing. reinitializeCodec(/* release= */ true);
break; break;
case KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION: case KEEP_CODEC_RESULT_YES_WITH_FLUSH:
keepingCodec = true; reinitializeCodec(/* release= */ false);
updateCodecOperatingRate();
break; break;
case KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION: case KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION:
if (!codecNeedsReconfigureWorkaround) { if (codecNeedsReconfigureWorkaround) {
keepingCodec = true; reinitializeCodec(/* release= */ true);
} else {
codecReconfigured = true; codecReconfigured = true;
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
codecNeedsAdaptationWorkaroundBuffer = codecNeedsAdaptationWorkaroundBuffer =
@ -1086,18 +1107,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|| (codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION || (codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION
&& format.width == oldFormat.width && format.width == oldFormat.width
&& format.height == oldFormat.height); && format.height == oldFormat.height);
updateCodecOperatingRate();
} }
break; break;
case KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION:
updateCodecOperatingRate();
break;
default: default:
throw new IllegalStateException(); // Never happens. throw new IllegalStateException(); // Never happens.
} }
} }
if (!keepingCodec) {
reinitializeCodec();
} else {
updateCodecOperatingRate();
}
} }
/** /**
@ -1211,13 +1230,15 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
this.codecOperatingRate = codecOperatingRate; this.codecOperatingRate = codecOperatingRate;
if (codec == null || codecReinitializationState != REINITIALIZATION_STATE_NONE) { if (codec == null
// Either no codec, or it's about to be reinitialized anyway. || (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 } else if (codecOperatingRate == CODEC_OPERATING_RATE_UNSET
&& codecConfiguredWithOperatingRate) { && codecConfiguredWithOperatingRate) {
// We need to clear the operating rate. The only way to do so is to instantiate a new codec // 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]. // instance. See [Internal ref: b/71987865].
reinitializeCodec(); reinitializeCodec(/* release= */ true);
} else if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET } else if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET
&& (codecConfiguredWithOperatingRate && (codecConfiguredWithOperatingRate
|| codecOperatingRate > assumedMinimumCodecOperatingRate)) { || 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 * Starts the process of re-initializing the codec. This may occur immediately, or be deferred
* immediately, or be deferred until any final output buffers have been dequeued. * 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. * @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; availableCodecInfos = null;
if (codecReceivedBuffers) { if (codecReceivedBuffers) {
// Signal end of stream and wait for any final output buffers before re-initialization. // Signal end of stream and wait for any final output buffers before re-initialization.
codecReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM; codecReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM;
} else { codecReinitializationIsRelease = release;
// There aren't any final output buffers, so perform re-initialization immediately. 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(); releaseCodec();
maybeInitCodec(); maybeInitCodec();
} }
@ -1449,8 +1478,12 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private void processEndOfStream() throws ExoPlaybackException { private void processEndOfStream() throws ExoPlaybackException {
if (codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) { if (codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) {
// We're waiting to re-initialize the codec, and have now processed all final buffers. // We're waiting to re-initialize the codec, and have now processed all final buffers.
releaseCodec(); if (codecReinitializationIsRelease) {
maybeInitCodec(); releaseCodec();
maybeInitCodec();
} else {
flushCodec();
}
} else { } else {
outputStreamEnded = true; outputStreamEnded = true;
renderToEndOfStream(); renderToEndOfStream();