Recreate Opus decoder for stream change

The framework opus decoder discards some samples after a call to
flush(). Because we flush a decoder that is being retained across an
input format change, this means that the start of audio gets truncated
when transitioning to a new opus stream. See also
https://android.googlesource.com/platform/frameworks/av/+/refs/heads/android10-release/media/libstagefright/codecs/opus/dec/SoftOpus.cpp.

Avoid this by recreating opus decoders instead of flushing them. It
seems fine to do this for all opus decoders as reinitialization should
be cheap, OEM-provided implementations may also discard samples and
playback shouldn't be interrupted on reinitialization due to the
downstream AudioTrack buffer.

PiperOrigin-RevId: 277458759
This commit is contained in:
andrewlewis 2019-10-30 08:36:39 +00:00 committed by Oliver Woodman
parent acb84396d5
commit f4b9042bf0
3 changed files with 32 additions and 24 deletions

View File

@ -1,6 +1,11 @@
# Release notes #
### 2.10.6 (2019-10-18) ###
### 2.10.7 ###
* Fix the start of audio getting truncated when transitioning to a new
item in a playlist of opus streams.
### 2.10.6 (2019-10-17) ###
* Add `Player.onPlaybackSuppressionReasonChanged` to allow listeners to
detect playbacks suppressions (e.g. transient audio focus loss) directly

View File

@ -434,13 +434,34 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
} else if (codecInfo.isSeamlessAdaptationSupported(
oldFormat, newFormat, /* isNewFormatComplete= */ true)) {
return KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION;
} else if (areCodecConfigurationCompatible(oldFormat, newFormat)) {
} else if (canKeepCodecWithFlush(oldFormat, newFormat)) {
return KEEP_CODEC_RESULT_YES_WITH_FLUSH;
} else {
return KEEP_CODEC_RESULT_NO;
}
}
/**
* Returns whether the codec can be flushed and reused when switching to a new format. Reuse is
* generally possible when the codec would be configured in an identical way after the format
* change (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 codec can be flushed and reused when switching to a new format.
*/
protected boolean canKeepCodecWithFlush(Format oldFormat, Format newFormat) {
// Flush and reuse the codec if the audio format and initialization data matches. For Opus, we
// don't flush and reuse the codec because the decoder may discard samples after flushing, which
// would result in audio being dropped just after a stream change (see [Internal: b/143450854]).
return Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType)
&& oldFormat.channelCount == newFormat.channelCount
&& oldFormat.sampleRate == newFormat.sampleRate
&& oldFormat.initializationDataEquals(newFormat)
&& !MimeTypes.AUDIO_OPUS.equals(oldFormat.sampleMimeType);
}
@Override
public MediaClock getMediaClock() {
return this;
@ -818,24 +839,6 @@ 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

@ -328,13 +328,13 @@ public final class MediaCodecInfo {
/**
* Whether the decoder supports video with a given width, height and frame rate.
* <p>
* Must not be called if the device SDK version is less than 21.
*
* <p>Must not be called if the device SDK version is less than 21.
*
* @param width Width in pixels.
* @param height Height in pixels.
* @param frameRate Optional frame rate in frames per second. Ignored if set to
* {@link Format#NO_VALUE} or any value less than or equal to 0.
* @param frameRate Optional frame rate in frames per second. Ignored if set to {@link
* Format#NO_VALUE} or any value less than or equal to 0.
* @return Whether the decoder supports video with the given width, height and frame rate.
*/
@TargetApi(21)