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());
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.

View File

@ -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();