Commit 028f3e64 by olly Committed by Oliver Woodman

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
parent 1dd0533a
......@@ -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;
}
......@@ -721,6 +729,24 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
/**
* 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.
*
......
......@@ -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();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment