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:
parent
1dd0533af6
commit
028f3e6438
@ -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.
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user