Report reasons for not being able to reuse decoders
PiperOrigin-RevId: 342344090
This commit is contained in:
parent
3ef609fa3b
commit
c47e62209d
@ -48,6 +48,12 @@
|
||||
* DRM:
|
||||
* Fix playback failure when switching from PlayReady protected content to
|
||||
Widevine or Clearkey protected content in a playlist.
|
||||
* Analytics:
|
||||
* Pass a `DecoderReuseEvaluation` to `AnalyticsListener`'s
|
||||
`onVideoInputFormatChanged` and `onAudioInputFormatChanged` methods. The
|
||||
`DecoderReuseEvaluation` indicates whether it was possible to re-use an
|
||||
existing decoder instance for the new format, and if not then the
|
||||
reasons why.
|
||||
* IMA extension:
|
||||
* Upgrade IMA SDK dependency to 3.21.0, and release the `AdsLoader`
|
||||
([#7344](https://github.com/google/ExoPlayer/issues/7344)).
|
||||
|
@ -15,12 +15,15 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.ext.av1;
|
||||
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.view.Surface;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.RendererCapabilities;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.TraceUtil;
|
||||
@ -164,7 +167,13 @@ public class Libgav1VideoRenderer extends DecoderVideoRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canKeepCodec(Format oldFormat, Format newFormat) {
|
||||
return true;
|
||||
protected DecoderReuseEvaluation canReuseDecoder(
|
||||
String decoderName, Format oldFormat, Format newFormat) {
|
||||
return new DecoderReuseEvaluation(
|
||||
decoderName,
|
||||
oldFormat,
|
||||
newFormat,
|
||||
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION,
|
||||
/* discardReasons= */ 0);
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,10 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.ext.ffmpeg;
|
||||
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_MIME_TYPE_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.view.Surface;
|
||||
import androidx.annotation.Nullable;
|
||||
@ -22,6 +26,7 @@ import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.RendererCapabilities;
|
||||
import com.google.android.exoplayer2.decoder.Decoder;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||
import com.google.android.exoplayer2.util.TraceUtil;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
@ -116,7 +121,15 @@ public final class FfmpegVideoRenderer extends DecoderVideoRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canKeepCodec(Format oldFormat, Format newFormat) {
|
||||
return Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType);
|
||||
protected DecoderReuseEvaluation canReuseDecoder(
|
||||
String decoderName, Format oldFormat, Format newFormat) {
|
||||
boolean sameMimeType = Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType);
|
||||
// TODO: Ability to reuse the decoder may be MIME type dependent.
|
||||
return new DecoderReuseEvaluation(
|
||||
decoderName,
|
||||
oldFormat,
|
||||
newFormat,
|
||||
sameMimeType ? REUSE_RESULT_YES_WITHOUT_RECONFIGURATION : REUSE_RESULT_NO,
|
||||
sameMimeType ? 0 : DISCARD_REASON_MIME_TYPE_CHANGED);
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.ext.vp9;
|
||||
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION;
|
||||
import static java.lang.Runtime.getRuntime;
|
||||
|
||||
import android.os.Handler;
|
||||
@ -23,6 +24,7 @@ import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.RendererCapabilities;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.TraceUtil;
|
||||
@ -169,7 +171,13 @@ public class LibvpxVideoRenderer extends DecoderVideoRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canKeepCodec(Format oldFormat, Format newFormat) {
|
||||
return true;
|
||||
protected DecoderReuseEvaluation canReuseDecoder(
|
||||
String decoderName, Format oldFormat, Format newFormat) {
|
||||
return new DecoderReuseEvaluation(
|
||||
decoderName,
|
||||
oldFormat,
|
||||
newFormat,
|
||||
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION,
|
||||
/* discardReasons= */ 0);
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ import com.google.android.exoplayer2.audio.AudioListener;
|
||||
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
||||
import com.google.android.exoplayer2.audio.AuxEffectInfo;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.device.DeviceInfo;
|
||||
import com.google.android.exoplayer2.device.DeviceListener;
|
||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||
@ -2242,10 +2243,11 @@ public class SimpleExoPlayer extends BasePlayer
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoInputFormatChanged(Format format) {
|
||||
public void onVideoInputFormatChanged(
|
||||
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||
videoFormat = format;
|
||||
for (VideoRendererEventListener videoDebugListener : videoDebugListeners) {
|
||||
videoDebugListener.onVideoInputFormatChanged(format);
|
||||
videoDebugListener.onVideoInputFormatChanged(format, decoderReuseEvaluation);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2337,10 +2339,11 @@ public class SimpleExoPlayer extends BasePlayer
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioInputFormatChanged(Format format) {
|
||||
public void onAudioInputFormatChanged(
|
||||
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||
audioFormat = format;
|
||||
for (AudioRendererEventListener audioDebugListener : audioDebugListeners) {
|
||||
audioDebugListener.onAudioInputFormatChanged(format);
|
||||
audioDebugListener.onAudioInputFormatChanged(format, decoderReuseEvaluation);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@ import com.google.android.exoplayer2.audio.AudioAttributes;
|
||||
import com.google.android.exoplayer2.audio.AudioListener;
|
||||
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.MetadataOutput;
|
||||
@ -195,11 +196,12 @@ public class AnalyticsCollector
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public final void onAudioInputFormatChanged(Format format) {
|
||||
public final void onAudioInputFormatChanged(
|
||||
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
listener -> {
|
||||
listener.onAudioInputFormatChanged(eventTime, format);
|
||||
listener.onAudioInputFormatChanged(eventTime, format, decoderReuseEvaluation);
|
||||
listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_AUDIO, format);
|
||||
});
|
||||
}
|
||||
@ -298,11 +300,12 @@ public class AnalyticsCollector
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public final void onVideoInputFormatChanged(Format format) {
|
||||
public final void onVideoInputFormatChanged(
|
||||
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
listeners.sendEvent(
|
||||
listener -> {
|
||||
listener.onVideoInputFormatChanged(eventTime, format);
|
||||
listener.onVideoInputFormatChanged(eventTime, format, decoderReuseEvaluation);
|
||||
listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_VIDEO, format);
|
||||
});
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.audio.AudioAttributes;
|
||||
import com.google.android.exoplayer2.audio.AudioSink;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.source.LoadEventInfo;
|
||||
import com.google.android.exoplayer2.source.MediaLoadData;
|
||||
@ -452,8 +453,8 @@ public interface AnalyticsListener {
|
||||
EventTime eventTime, int trackType, String decoderName, long initializationDurationMs) {}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #onAudioInputFormatChanged} and {@link #onVideoInputFormatChanged}
|
||||
* instead.
|
||||
* @deprecated Use {@link #onAudioInputFormatChanged(EventTime, Format, DecoderReuseEvaluation)}
|
||||
* and {@link #onVideoInputFormatChanged(EventTime, Format, DecoderReuseEvaluation)}. instead.
|
||||
*/
|
||||
@Deprecated
|
||||
default void onDecoderInputFormatChanged(EventTime eventTime, int trackType, Format format) {}
|
||||
@ -482,13 +483,26 @@ public interface AnalyticsListener {
|
||||
default void onAudioDecoderInitialized(
|
||||
EventTime eventTime, String decoderName, long initializationDurationMs) {}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #onAudioInputFormatChanged(EventTime, Format, DecoderReuseEvaluation)}.
|
||||
*/
|
||||
@Deprecated
|
||||
default void onAudioInputFormatChanged(EventTime eventTime, Format format) {}
|
||||
|
||||
/**
|
||||
* Called when the format of the media being consumed by an audio renderer changes.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param format The new format.
|
||||
* @param decoderReuseEvaluation The result of the evaluation to determine whether an existing
|
||||
* decoder instance can be reused for the new format, or {@code null} if the renderer did not
|
||||
* have a decoder.
|
||||
*/
|
||||
default void onAudioInputFormatChanged(EventTime eventTime, Format format) {}
|
||||
@SuppressWarnings("deprecation")
|
||||
default void onAudioInputFormatChanged(
|
||||
EventTime eventTime, Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||
onAudioInputFormatChanged(eventTime, format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the audio position has increased for the first time since the last pause or
|
||||
@ -589,13 +603,26 @@ public interface AnalyticsListener {
|
||||
default void onVideoDecoderInitialized(
|
||||
EventTime eventTime, String decoderName, long initializationDurationMs) {}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #onVideoInputFormatChanged(EventTime, Format, DecoderReuseEvaluation)}.
|
||||
*/
|
||||
@Deprecated
|
||||
default void onVideoInputFormatChanged(EventTime eventTime, Format format) {}
|
||||
|
||||
/**
|
||||
* Called when the format of the media being consumed by a video renderer changes.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param format The new format.
|
||||
* @param decoderReuseEvaluation The result of the evaluation to determine whether an existing
|
||||
* decoder instance can be reused for the new format, or {@code null} if the renderer did not
|
||||
* have a decoder.
|
||||
*/
|
||||
default void onVideoInputFormatChanged(EventTime eventTime, Format format) {}
|
||||
@SuppressWarnings("deprecation")
|
||||
default void onVideoInputFormatChanged(
|
||||
EventTime eventTime, Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||
onVideoInputFormatChanged(eventTime, format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after video frames have been dropped.
|
||||
|
@ -27,6 +27,7 @@ import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Renderer;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
|
||||
/**
|
||||
@ -61,12 +62,23 @@ public interface AudioRendererEventListener {
|
||||
default void onAudioDecoderInitialized(
|
||||
String decoderName, long initializedTimestampMs, long initializationDurationMs) {}
|
||||
|
||||
/** @deprecated Use {@link #onAudioInputFormatChanged(Format, DecoderReuseEvaluation)}. */
|
||||
@Deprecated
|
||||
default void onAudioInputFormatChanged(Format format) {}
|
||||
|
||||
/**
|
||||
* Called when the format of the media being consumed by the renderer changes.
|
||||
*
|
||||
* @param format The new format.
|
||||
* @param decoderReuseEvaluation The result of the evaluation to determine whether an existing
|
||||
* decoder instance can be reused for the new format, or {@code null} if the renderer did not
|
||||
* have a decoder.
|
||||
*/
|
||||
default void onAudioInputFormatChanged(Format format) {}
|
||||
@SuppressWarnings("deprecation")
|
||||
default void onAudioInputFormatChanged(
|
||||
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||
onAudioInputFormatChanged(format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the audio position has increased for the first time since the last pause or
|
||||
@ -167,9 +179,11 @@ public interface AudioRendererEventListener {
|
||||
}
|
||||
|
||||
/** Invokes {@link AudioRendererEventListener#onAudioInputFormatChanged(Format)}. */
|
||||
public void inputFormatChanged(Format format) {
|
||||
public void inputFormatChanged(
|
||||
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||
if (handler != null) {
|
||||
handler.post(() -> castNonNull(listener).onAudioInputFormatChanged(format));
|
||||
handler.post(
|
||||
() -> castNonNull(listener).onAudioInputFormatChanged(format, decoderReuseEvaluation));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,9 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.audio;
|
||||
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_DRM_SESSION_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_REUSE_NOT_IMPLEMENTED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import android.media.audiofx.Virtualizer;
|
||||
@ -38,6 +41,7 @@ import com.google.android.exoplayer2.decoder.Decoder;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderException;
|
||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
|
||||
import com.google.android.exoplayer2.drm.DrmSession;
|
||||
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
|
||||
@ -347,14 +351,19 @@ public abstract class DecoderAudioRenderer<
|
||||
protected abstract Format getOutputFormat(T decoder);
|
||||
|
||||
/**
|
||||
* Returns whether the existing decoder can be kept for a new format.
|
||||
* Evaluates whether the existing decoder can be reused for a new {@link Format}.
|
||||
*
|
||||
* <p>The default implementation does not allow decoder reuse.
|
||||
*
|
||||
* @param decoderName The name of the decoder.
|
||||
* @param oldFormat The previous format.
|
||||
* @param newFormat The new format.
|
||||
* @return Whether the existing decoder can be kept.
|
||||
* @return The result of the evaluation.
|
||||
*/
|
||||
protected boolean canKeepCodec(Format oldFormat, Format newFormat) {
|
||||
return false;
|
||||
protected DecoderReuseEvaluation canReuseDecoder(
|
||||
String decoderName, Format oldFormat, Format newFormat) {
|
||||
return new DecoderReuseEvaluation(
|
||||
decoderName, oldFormat, newFormat, REUSE_RESULT_NO, DISCARD_REASON_REUSE_NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
private boolean drainOutputBuffer()
|
||||
@ -655,10 +664,29 @@ public abstract class DecoderAudioRenderer<
|
||||
setSourceDrmSession(formatHolder.drmSession);
|
||||
Format oldFormat = inputFormat;
|
||||
inputFormat = newFormat;
|
||||
encoderDelay = newFormat.encoderDelay;
|
||||
encoderPadding = newFormat.encoderPadding;
|
||||
|
||||
if (decoder == null) {
|
||||
maybeInitDecoder();
|
||||
} else if (sourceDrmSession != decoderDrmSession || !canKeepCodec(oldFormat, inputFormat)) {
|
||||
eventDispatcher.inputFormatChanged(inputFormat, /* decoderReuseEvaluation= */ null);
|
||||
return;
|
||||
}
|
||||
|
||||
DecoderReuseEvaluation evaluation;
|
||||
if (sourceDrmSession != decoderDrmSession) {
|
||||
evaluation =
|
||||
new DecoderReuseEvaluation(
|
||||
decoder.getName(),
|
||||
oldFormat,
|
||||
newFormat,
|
||||
REUSE_RESULT_NO,
|
||||
DISCARD_REASON_DRM_SESSION_CHANGED);
|
||||
} else {
|
||||
evaluation = canReuseDecoder(decoder.getName(), oldFormat, newFormat);
|
||||
}
|
||||
|
||||
if (evaluation.result == REUSE_RESULT_NO) {
|
||||
if (decoderReceivedBuffers) {
|
||||
// Signal end of stream and wait for any final output buffers before re-initialization.
|
||||
decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM;
|
||||
@ -669,10 +697,7 @@ public abstract class DecoderAudioRenderer<
|
||||
audioTrackNeedsConfigure = true;
|
||||
}
|
||||
}
|
||||
|
||||
encoderDelay = inputFormat.encoderDelay;
|
||||
encoderPadding = inputFormat.encoderPadding;
|
||||
eventDispatcher.inputFormatChanged(inputFormat);
|
||||
eventDispatcher.inputFormatChanged(inputFormat, evaluation);
|
||||
}
|
||||
|
||||
private void onQueueInputBuffer(DecoderInputBuffer buffer) {
|
||||
|
@ -15,7 +15,8 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.audio;
|
||||
|
||||
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_NO;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO;
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
@ -41,9 +42,10 @@ import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispa
|
||||
import com.google.android.exoplayer2.audio.AudioSink.InitializationException;
|
||||
import com.google.android.exoplayer2.audio.AudioSink.WriteException;
|
||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DecoderDiscardReasons;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KeepCodecResult;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
|
||||
@ -328,13 +330,21 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||
}
|
||||
|
||||
@Override
|
||||
@KeepCodecResult
|
||||
protected int canKeepCodec(
|
||||
MediaCodecAdapter codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
|
||||
protected DecoderReuseEvaluation canReuseCodec(
|
||||
MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
|
||||
DecoderReuseEvaluation evaluation = codecInfo.canReuseCodec(oldFormat, newFormat);
|
||||
|
||||
@DecoderDiscardReasons int discardReasons = evaluation.discardReasons;
|
||||
if (getCodecMaxInputSize(codecInfo, newFormat) > codecMaxInputSize) {
|
||||
return KEEP_CODEC_RESULT_NO;
|
||||
discardReasons |= DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED;
|
||||
}
|
||||
return codecInfo.canKeepCodec(oldFormat, newFormat);
|
||||
|
||||
return new DecoderReuseEvaluation(
|
||||
codecInfo.name,
|
||||
oldFormat,
|
||||
newFormat,
|
||||
discardReasons != 0 ? REUSE_RESULT_NO : evaluation.result,
|
||||
discardReasons);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -370,9 +380,12 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
|
||||
super.onInputFormatChanged(formatHolder);
|
||||
eventDispatcher.inputFormatChanged(formatHolder.format);
|
||||
@Nullable
|
||||
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
|
||||
throws ExoPlaybackException {
|
||||
@Nullable DecoderReuseEvaluation evaluation = super.onInputFormatChanged(formatHolder);
|
||||
eventDispatcher.inputFormatChanged(formatHolder.format, evaluation);
|
||||
return evaluation;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -664,7 +677,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||
return maxInputSize;
|
||||
}
|
||||
for (Format streamFormat : streamFormats) {
|
||||
if (codecInfo.canKeepCodec(format, streamFormat) != KEEP_CODEC_RESULT_NO) {
|
||||
if (codecInfo.canReuseCodec(format, streamFormat).result != REUSE_RESULT_NO) {
|
||||
maxInputSize = max(maxInputSize, getCodecMaxInputSize(codecInfo, streamFormat));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.decoder;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotEmpty;
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.video.ColorInfo;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* The result of an evaluation to determine whether a decoder can be reused for a new input format.
|
||||
*/
|
||||
public final class DecoderReuseEvaluation {
|
||||
|
||||
/** Possible outcomes of the evaluation. */
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
REUSE_RESULT_NO,
|
||||
REUSE_RESULT_YES_WITH_FLUSH,
|
||||
REUSE_RESULT_YES_WITH_RECONFIGURATION,
|
||||
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION
|
||||
})
|
||||
public @interface DecoderReuseResult {}
|
||||
/** The decoder cannot be reused. */
|
||||
public static final int REUSE_RESULT_NO = 0;
|
||||
/** The decoder can be reused, but must be flushed. */
|
||||
public static final int REUSE_RESULT_YES_WITH_FLUSH = 1;
|
||||
/**
|
||||
* The decoder can be reused. It does not need to be flushed, but must be reconfigured by
|
||||
* prefixing the next input buffer with the new format's configuration data.
|
||||
*/
|
||||
public static final int REUSE_RESULT_YES_WITH_RECONFIGURATION = 2;
|
||||
/** The decoder can be kept. It does not need to be flushed and no reconfiguration is required. */
|
||||
public static final int REUSE_RESULT_YES_WITHOUT_RECONFIGURATION = 3;
|
||||
|
||||
/** Possible reasons why reuse is not possible. */
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(
|
||||
flag = true,
|
||||
value = {
|
||||
DISCARD_REASON_REUSE_NOT_IMPLEMENTED,
|
||||
DISCARD_REASON_WORKAROUND,
|
||||
DISCARD_REASON_APP_OVERRIDE,
|
||||
DISCARD_REASON_MIME_TYPE_CHANGED,
|
||||
DISCARD_REASON_OPERATING_RATE_CHANGED,
|
||||
DISCARD_REASON_INITIALIZATION_DATA_CHANGED,
|
||||
DISCARD_REASON_DRM_SESSION_CHANGED,
|
||||
DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED,
|
||||
DISCARD_REASON_VIDEO_MAX_RESOLUTION_EXCEEDED,
|
||||
DISCARD_REASON_VIDEO_RESOLUTION_CHANGED,
|
||||
DISCARD_REASON_VIDEO_ROTATION_CHANGED,
|
||||
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED,
|
||||
DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED,
|
||||
DISCARD_REASON_AUDIO_SAMPLE_RATE_CHANGED,
|
||||
DISCARD_REASON_AUDIO_ENCODING_CHANGED
|
||||
})
|
||||
public @interface DecoderDiscardReasons {}
|
||||
|
||||
/** Decoder reuse is not implemented. */
|
||||
public static final int DISCARD_REASON_REUSE_NOT_IMPLEMENTED = 1 << 0;
|
||||
/** Decoder reuse is disabled by a workaround. */
|
||||
public static final int DISCARD_REASON_WORKAROUND = 1 << 1;
|
||||
/** Decoder reuse is disabled by overriding behavior in application code. */
|
||||
public static final int DISCARD_REASON_APP_OVERRIDE = 1 << 2;
|
||||
/** The sample MIME type is changing. */
|
||||
public static final int DISCARD_REASON_MIME_TYPE_CHANGED = 1 << 3;
|
||||
/** The codec's operating rate is changing. */
|
||||
public static final int DISCARD_REASON_OPERATING_RATE_CHANGED = 1 << 4;
|
||||
/** The format initialization data is changing. */
|
||||
public static final int DISCARD_REASON_INITIALIZATION_DATA_CHANGED = 1 << 5;
|
||||
/** The new format may exceed the decoder's configured maximum sample size, in bytes. */
|
||||
public static final int DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED = 1 << 6;
|
||||
/** The DRM session is changing. */
|
||||
public static final int DISCARD_REASON_DRM_SESSION_CHANGED = 1 << 7;
|
||||
/** The new format may exceed the decoder's configured maximum resolution. */
|
||||
public static final int DISCARD_REASON_VIDEO_MAX_RESOLUTION_EXCEEDED = 1 << 8;
|
||||
/** The video resolution is changing. */
|
||||
public static final int DISCARD_REASON_VIDEO_RESOLUTION_CHANGED = 1 << 9;
|
||||
/** The video rotation is changing. */
|
||||
public static final int DISCARD_REASON_VIDEO_ROTATION_CHANGED = 1 << 10;
|
||||
/** The video {@link ColorInfo} is changing. */
|
||||
public static final int DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED = 1 << 11;
|
||||
/** The audio channel count is changing. */
|
||||
public static final int DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED = 1 << 12;
|
||||
/** The audio sample rate is changing. */
|
||||
public static final int DISCARD_REASON_AUDIO_SAMPLE_RATE_CHANGED = 1 << 13;
|
||||
/** The audio encoding is changing. */
|
||||
public static final int DISCARD_REASON_AUDIO_ENCODING_CHANGED = 1 << 14;
|
||||
|
||||
/** The name of the decoder. */
|
||||
public final String decoderName;
|
||||
|
||||
/** The {@link Format} for which the decoder was previously configured. */
|
||||
public final Format oldFormat;
|
||||
|
||||
/** The new {@link Format} being evaluated. */
|
||||
public final Format newFormat;
|
||||
|
||||
/** The {@link DecoderReuseResult result} of the evaluation. */
|
||||
@DecoderReuseResult public final int result;
|
||||
|
||||
/**
|
||||
* {@link DecoderDiscardReasons Reasons} why the decoder cannot be reused. Always {@code 0} if
|
||||
* reuse is possible. May also be {code 0} if reuse is not possible for an unspecified reason.
|
||||
*/
|
||||
@DecoderDiscardReasons public final int discardReasons;
|
||||
|
||||
/**
|
||||
* @param decoderName The name of the decoder.
|
||||
* @param oldFormat The {@link Format} for which the decoder was previously configured.
|
||||
* @param newFormat The new {@link Format} being evaluated.
|
||||
* @param result The {@link DecoderReuseResult result} of the evaluation.
|
||||
* @param discardReasons One or more {@link DecoderDiscardReasons reasons} why the decoder cannot
|
||||
* be reused, or {@code 0} if reuse is possible.
|
||||
*/
|
||||
public DecoderReuseEvaluation(
|
||||
String decoderName,
|
||||
Format oldFormat,
|
||||
Format newFormat,
|
||||
@DecoderReuseResult int result,
|
||||
@DecoderDiscardReasons int discardReasons) {
|
||||
checkArgument(result == REUSE_RESULT_NO || discardReasons == 0);
|
||||
this.decoderName = checkNotEmpty(decoderName);
|
||||
this.oldFormat = checkNotNull(oldFormat);
|
||||
this.newFormat = checkNotNull(newFormat);
|
||||
this.result = result;
|
||||
this.discardReasons = discardReasons;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
DecoderReuseEvaluation other = (DecoderReuseEvaluation) obj;
|
||||
return result == other.result
|
||||
&& discardReasons == other.discardReasons
|
||||
&& decoderName.equals(other.decoderName)
|
||||
&& oldFormat.equals(other.oldFormat)
|
||||
&& newFormat.equals(other.newFormat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashCode = 17;
|
||||
hashCode = 31 * hashCode + result;
|
||||
hashCode = 31 * hashCode + discardReasons;
|
||||
hashCode = 31 * hashCode + decoderName.hashCode();
|
||||
hashCode = 31 * hashCode + oldFormat.hashCode();
|
||||
hashCode = 31 * hashCode + newFormat.hashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
@ -15,6 +15,20 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.mediacodec;
|
||||
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_AUDIO_ENCODING_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_AUDIO_SAMPLE_RATE_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_INITIALIZATION_DATA_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_MIME_TYPE_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_RESOLUTION_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_ROTATION_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_WORKAROUND;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITH_FLUSH;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITH_RECONFIGURATION;
|
||||
|
||||
import android.graphics.Point;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo.AudioCapabilities;
|
||||
@ -22,18 +36,17 @@ import android.media.MediaCodecInfo.CodecCapabilities;
|
||||
import android.media.MediaCodecInfo.CodecProfileLevel;
|
||||
import android.media.MediaCodecInfo.VideoCapabilities;
|
||||
import android.util.Pair;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DecoderDiscardReasons;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DecoderReuseResult;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/** Information about a {@link MediaCodec} for a given mime type. */
|
||||
@SuppressWarnings("InlinedApi")
|
||||
@ -47,28 +60,6 @@ public final class MediaCodecInfo {
|
||||
*/
|
||||
public static final int MAX_SUPPORTED_INSTANCES_UNKNOWN = -1;
|
||||
|
||||
/** The possible return values for {@link #canKeepCodec(Format, Format)}. */
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
KEEP_CODEC_RESULT_NO,
|
||||
KEEP_CODEC_RESULT_YES_WITH_FLUSH,
|
||||
KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION,
|
||||
KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION
|
||||
})
|
||||
public @interface KeepCodecResult {}
|
||||
/** The codec cannot be kept. */
|
||||
public static final int KEEP_CODEC_RESULT_NO = 0;
|
||||
/** The codec can be kept, but must be flushed. */
|
||||
public static final int KEEP_CODEC_RESULT_YES_WITH_FLUSH = 1;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public 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. */
|
||||
public static final int KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION = 3;
|
||||
|
||||
/**
|
||||
* The name of the decoder.
|
||||
*
|
||||
@ -361,7 +352,7 @@ public final class MediaCodecInfo {
|
||||
* @param isNewFormatComplete Whether {@code newFormat} is populated with format-specific
|
||||
* metadata.
|
||||
* @return Whether it is possible to adapt the decoder seamlessly.
|
||||
* @deprecated Use {@link #canKeepCodec}.
|
||||
* @deprecated Use {@link #canReuseCodec}.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isSeamlessAdaptationSupported(
|
||||
@ -369,49 +360,68 @@ public final class MediaCodecInfo {
|
||||
if (!isNewFormatComplete && oldFormat.colorInfo != null && newFormat.colorInfo == null) {
|
||||
newFormat = newFormat.buildUpon().setColorInfo(oldFormat.colorInfo).build();
|
||||
}
|
||||
@KeepCodecResult int keepCodecResult = canKeepCodec(oldFormat, newFormat);
|
||||
return keepCodecResult == KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION
|
||||
|| keepCodecResult == KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION;
|
||||
@DecoderReuseResult int reuseResult = canReuseCodec(oldFormat, newFormat).result;
|
||||
return reuseResult == REUSE_RESULT_YES_WITH_RECONFIGURATION
|
||||
|| reuseResult == REUSE_RESULT_YES_WITHOUT_RECONFIGURATION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extent to which it's possible to adapt an instance of this decoder that's currently
|
||||
* decoding {@code oldFormat} to decode {@code newFormat} instead.
|
||||
* Evaluates whether it's possible to reuse an instance of this decoder that's currently decoding
|
||||
* {@code oldFormat} to decode {@code newFormat} instead.
|
||||
*
|
||||
* <p>For adaptation to succeed, the codec must also be configured with maximum values that are
|
||||
* compatible with the new format.
|
||||
*
|
||||
* @param oldFormat The format being decoded.
|
||||
* @param newFormat The new format.
|
||||
* @return The extent to which it's possible to adapt an instance of the decoder.
|
||||
* @return The result of the evaluation.
|
||||
*/
|
||||
@KeepCodecResult
|
||||
public int canKeepCodec(Format oldFormat, Format newFormat) {
|
||||
public DecoderReuseEvaluation canReuseCodec(Format oldFormat, Format newFormat) {
|
||||
@DecoderDiscardReasons int discardReasons = 0;
|
||||
if (!Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType)) {
|
||||
return KEEP_CODEC_RESULT_NO;
|
||||
discardReasons |= DISCARD_REASON_MIME_TYPE_CHANGED;
|
||||
}
|
||||
|
||||
if (isVideo) {
|
||||
if (oldFormat.rotationDegrees == newFormat.rotationDegrees
|
||||
&& (adaptive
|
||||
|| (oldFormat.width == newFormat.width && oldFormat.height == newFormat.height))
|
||||
&& Util.areEqual(oldFormat.colorInfo, newFormat.colorInfo)) {
|
||||
if (oldFormat.initializationDataEquals(newFormat)) {
|
||||
return KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION;
|
||||
} else if (!needsAdaptationReconfigureWorkaround(name)) {
|
||||
return KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION;
|
||||
}
|
||||
if (oldFormat.rotationDegrees != newFormat.rotationDegrees) {
|
||||
discardReasons |= DISCARD_REASON_VIDEO_ROTATION_CHANGED;
|
||||
}
|
||||
if (!adaptive
|
||||
&& (oldFormat.width != newFormat.width || oldFormat.height != newFormat.height)) {
|
||||
discardReasons |= DISCARD_REASON_VIDEO_RESOLUTION_CHANGED;
|
||||
}
|
||||
if (!Util.areEqual(oldFormat.colorInfo, newFormat.colorInfo)) {
|
||||
discardReasons |= DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED;
|
||||
}
|
||||
if (needsAdaptationReconfigureWorkaround(name)
|
||||
&& !oldFormat.initializationDataEquals(newFormat)) {
|
||||
discardReasons |= DISCARD_REASON_WORKAROUND;
|
||||
}
|
||||
|
||||
if (discardReasons == 0) {
|
||||
return new DecoderReuseEvaluation(
|
||||
name,
|
||||
oldFormat,
|
||||
newFormat,
|
||||
oldFormat.initializationDataEquals(newFormat)
|
||||
? REUSE_RESULT_YES_WITHOUT_RECONFIGURATION
|
||||
: REUSE_RESULT_YES_WITH_RECONFIGURATION,
|
||||
/* discardReasons= */ 0);
|
||||
}
|
||||
} else {
|
||||
if (oldFormat.channelCount != newFormat.channelCount
|
||||
|| oldFormat.sampleRate != newFormat.sampleRate
|
||||
|| oldFormat.pcmEncoding != newFormat.pcmEncoding) {
|
||||
return KEEP_CODEC_RESULT_NO;
|
||||
if (oldFormat.channelCount != newFormat.channelCount) {
|
||||
discardReasons |= DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED;
|
||||
}
|
||||
if (oldFormat.sampleRate != newFormat.sampleRate) {
|
||||
discardReasons |= DISCARD_REASON_AUDIO_SAMPLE_RATE_CHANGED;
|
||||
}
|
||||
if (oldFormat.pcmEncoding != newFormat.pcmEncoding) {
|
||||
discardReasons |= DISCARD_REASON_AUDIO_ENCODING_CHANGED;
|
||||
}
|
||||
|
||||
// Check whether we're adapting between two xHE-AAC formats, for which adaptation is possible
|
||||
// without reconfiguration or flushing.
|
||||
if (MimeTypes.AUDIO_AAC.equals(mimeType)) {
|
||||
if (discardReasons == 0 && MimeTypes.AUDIO_AAC.equals(mimeType)) {
|
||||
@Nullable
|
||||
Pair<Integer, Integer> oldCodecProfileLevel =
|
||||
MediaCodecUtil.getCodecProfileAndLevel(oldFormat);
|
||||
@ -423,18 +433,30 @@ public final class MediaCodecInfo {
|
||||
int newProfile = newCodecProfileLevel.first;
|
||||
if (oldProfile == CodecProfileLevel.AACObjectXHE
|
||||
&& newProfile == CodecProfileLevel.AACObjectXHE) {
|
||||
return KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION;
|
||||
return new DecoderReuseEvaluation(
|
||||
name,
|
||||
oldFormat,
|
||||
newFormat,
|
||||
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION,
|
||||
/* discardReasons= */ 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (oldFormat.initializationDataEquals(newFormat)
|
||||
&& !needsAdaptationFlushWorkaround(mimeType)) {
|
||||
return KEEP_CODEC_RESULT_YES_WITH_FLUSH;
|
||||
if (!oldFormat.initializationDataEquals(newFormat)) {
|
||||
discardReasons |= DISCARD_REASON_INITIALIZATION_DATA_CHANGED;
|
||||
}
|
||||
if (needsAdaptationFlushWorkaround(mimeType)) {
|
||||
discardReasons |= DISCARD_REASON_WORKAROUND;
|
||||
}
|
||||
|
||||
if (discardReasons == 0) {
|
||||
return new DecoderReuseEvaluation(
|
||||
name, oldFormat, newFormat, REUSE_RESULT_YES_WITH_FLUSH, /* discardReasons= */ 0);
|
||||
}
|
||||
}
|
||||
|
||||
return KEEP_CODEC_RESULT_NO;
|
||||
return new DecoderReuseEvaluation(name, oldFormat, newFormat, REUSE_RESULT_NO, discardReasons);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,10 +15,14 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.mediacodec;
|
||||
|
||||
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_NO;
|
||||
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION;
|
||||
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_YES_WITH_FLUSH;
|
||||
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_DRM_SESSION_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_OPERATING_RATE_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_REUSE_NOT_IMPLEMENTED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_WORKAROUND;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITH_FLUSH;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITH_RECONFIGURATION;
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkState;
|
||||
import static java.lang.Math.max;
|
||||
@ -44,11 +48,12 @@ import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.FormatHolder;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DecoderDiscardReasons;
|
||||
import com.google.android.exoplayer2.drm.DrmSession;
|
||||
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
|
||||
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KeepCodecResult;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.source.SampleStream;
|
||||
@ -710,7 +715,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
if (codec != null
|
||||
&& codecDrainAction != DRAIN_ACTION_REINITIALIZE
|
||||
&& getState() != STATE_DISABLED) {
|
||||
updateOperatingRateOrReinitializeCodec(codecInputFormat);
|
||||
updateCodecOperatingRate(codecInputFormat);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1375,9 +1380,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
*
|
||||
* @param formatHolder A {@link FormatHolder} that holds the new {@link Format}.
|
||||
* @throws ExoPlaybackException If an error occurs re-initializing the {@link MediaCodec}.
|
||||
* @return The result of the evaluation to determine whether the existing decoder instance can be
|
||||
* reused for the new format, or {@code null} if the renderer did not have a decoder.
|
||||
*/
|
||||
@CallSuper
|
||||
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
|
||||
@Nullable
|
||||
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
|
||||
throws ExoPlaybackException {
|
||||
waitingForFirstSampleInFormat = true;
|
||||
Format newFormat = checkNotNull(formatHolder.format);
|
||||
setSourceDrmSession(formatHolder.drmSession);
|
||||
@ -1385,7 +1394,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
|
||||
if (bypassEnabled) {
|
||||
bypassDrainAndReinitialize = true;
|
||||
return; // Need to drain batch buffer first.
|
||||
return null; // Need to drain batch buffer first.
|
||||
}
|
||||
|
||||
if (codec == null) {
|
||||
@ -1393,66 +1402,86 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
availableCodecInfos = null;
|
||||
}
|
||||
maybeInitCodecOrBypass();
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
// We have an existing codec that we may need to reconfigure, re-initialize, or release to
|
||||
// switch to bypass. If the existing codec instance is kept then its operating rate and DRM
|
||||
// session may need to be updated.
|
||||
|
||||
MediaCodecAdapter oldCodec = codec;
|
||||
Format oldFormat = codecInputFormat;
|
||||
if (drmNeedsCodecReinitialization(codecInfo, newFormat, codecDrmSession, sourceDrmSession)) {
|
||||
drainAndReinitializeCodec();
|
||||
return;
|
||||
return new DecoderReuseEvaluation(
|
||||
codecInfo.name,
|
||||
oldFormat,
|
||||
newFormat,
|
||||
REUSE_RESULT_NO,
|
||||
DISCARD_REASON_DRM_SESSION_CHANGED);
|
||||
}
|
||||
boolean drainAndUpdateCodecDrmSession = sourceDrmSession != codecDrmSession;
|
||||
Assertions.checkState(!drainAndUpdateCodecDrmSession || Util.SDK_INT >= 23);
|
||||
|
||||
switch (canKeepCodec(codec, codecInfo, codecInputFormat, newFormat)) {
|
||||
case KEEP_CODEC_RESULT_NO:
|
||||
DecoderReuseEvaluation evaluation = canReuseCodec(codecInfo, oldFormat, newFormat);
|
||||
@DecoderDiscardReasons int overridingDiscardReasons = 0;
|
||||
switch (evaluation.result) {
|
||||
case REUSE_RESULT_NO:
|
||||
drainAndReinitializeCodec();
|
||||
break;
|
||||
case KEEP_CODEC_RESULT_YES_WITH_FLUSH:
|
||||
if (updateOperatingRateOrReinitializeCodec(newFormat)) {
|
||||
// Codec re-initialization triggered.
|
||||
case REUSE_RESULT_YES_WITH_FLUSH:
|
||||
if (!updateCodecOperatingRate(newFormat)) {
|
||||
overridingDiscardReasons |= DISCARD_REASON_OPERATING_RATE_CHANGED;
|
||||
} else {
|
||||
codecInputFormat = newFormat;
|
||||
if (drainAndUpdateCodecDrmSession) {
|
||||
drainAndUpdateCodecDrmSessionV23();
|
||||
} else {
|
||||
drainAndFlushCodec();
|
||||
if (!drainAndUpdateCodecDrmSessionV23()) {
|
||||
overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
|
||||
}
|
||||
} else if (!drainAndFlushCodec()) {
|
||||
overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION:
|
||||
if (updateOperatingRateOrReinitializeCodec(newFormat)) {
|
||||
// Codec re-initialization triggered.
|
||||
case REUSE_RESULT_YES_WITH_RECONFIGURATION:
|
||||
if (!updateCodecOperatingRate(newFormat)) {
|
||||
overridingDiscardReasons |= DISCARD_REASON_OPERATING_RATE_CHANGED;
|
||||
} else {
|
||||
codecReconfigured = true;
|
||||
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
|
||||
codecNeedsAdaptationWorkaroundBuffer =
|
||||
codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_ALWAYS
|
||||
|| (codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION
|
||||
&& newFormat.width == codecInputFormat.width
|
||||
&& newFormat.height == codecInputFormat.height);
|
||||
&& newFormat.width == oldFormat.width
|
||||
&& newFormat.height == oldFormat.height);
|
||||
codecInputFormat = newFormat;
|
||||
if (drainAndUpdateCodecDrmSession) {
|
||||
drainAndUpdateCodecDrmSessionV23();
|
||||
if (drainAndUpdateCodecDrmSession && !drainAndUpdateCodecDrmSessionV23()) {
|
||||
overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION:
|
||||
if (updateOperatingRateOrReinitializeCodec(newFormat)) {
|
||||
// Codec re-initialization triggered.
|
||||
case REUSE_RESULT_YES_WITHOUT_RECONFIGURATION:
|
||||
if (!updateCodecOperatingRate(newFormat)) {
|
||||
overridingDiscardReasons |= DISCARD_REASON_OPERATING_RATE_CHANGED;
|
||||
} else {
|
||||
codecInputFormat = newFormat;
|
||||
if (drainAndUpdateCodecDrmSession) {
|
||||
drainAndUpdateCodecDrmSessionV23();
|
||||
if (drainAndUpdateCodecDrmSession && !drainAndUpdateCodecDrmSessionV23()) {
|
||||
overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException(); // Never happens.
|
||||
}
|
||||
|
||||
if (evaluation.result != REUSE_RESULT_NO
|
||||
&& (codec != oldCodec || codecDrainAction == DRAIN_ACTION_REINITIALIZE)) {
|
||||
// Initial evaluation indicated reuse was possible, but codec re-initialization was triggered.
|
||||
// The reasons are indicated by overridingDiscardReasons.
|
||||
return new DecoderReuseEvaluation(
|
||||
codecInfo.name, oldFormat, newFormat, REUSE_RESULT_NO, overridingDiscardReasons);
|
||||
}
|
||||
|
||||
return evaluation;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1544,21 +1573,24 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the existing {@link MediaCodec} can be kept for a new {@link Format}, and if
|
||||
* Evaluates whether the existing {@link MediaCodec} can be kept for a new {@link Format}, and if
|
||||
* it can whether it requires reconfiguration.
|
||||
*
|
||||
* <p>The default implementation returns {@link MediaCodecInfo#KEEP_CODEC_RESULT_NO}.
|
||||
* <p>The default implementation does not allow decoder reuse.
|
||||
*
|
||||
* @param codec The existing {@link MediaCodecAdapter} instance.
|
||||
* @param codecInfo A {@link MediaCodecInfo} describing the decoder.
|
||||
* @param oldFormat The {@link Format} for which the existing instance is configured.
|
||||
* @param newFormat The new {@link Format}.
|
||||
* @return Whether the instance can be kept, and if it can whether it requires reconfiguration.
|
||||
* @return The result of the evaluation.
|
||||
*/
|
||||
@KeepCodecResult
|
||||
protected int canKeepCodec(
|
||||
MediaCodecAdapter codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
|
||||
return KEEP_CODEC_RESULT_NO;
|
||||
protected DecoderReuseEvaluation canReuseCodec(
|
||||
MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
|
||||
return new DecoderReuseEvaluation(
|
||||
codecInfo.name,
|
||||
oldFormat,
|
||||
newFormat,
|
||||
REUSE_RESULT_NO,
|
||||
DISCARD_REASON_REUSE_NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1608,25 +1640,23 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
*
|
||||
* @param format The {@link Format} for which the operating rate should be configured.
|
||||
* @throws ExoPlaybackException If an error occurs releasing or initializing a codec.
|
||||
* @return Whether codec release and re-initialization was triggered, rather than the existing
|
||||
* codec being updated.
|
||||
* @return False if codec release and re-initialization was triggered. True in all other cases.
|
||||
*/
|
||||
private boolean updateOperatingRateOrReinitializeCodec(Format format)
|
||||
throws ExoPlaybackException {
|
||||
private boolean updateCodecOperatingRate(Format format) throws ExoPlaybackException {
|
||||
if (Util.SDK_INT < 23) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
float newCodecOperatingRate =
|
||||
getCodecOperatingRateV23(playbackSpeed, format, getStreamFormats());
|
||||
if (codecOperatingRate == newCodecOperatingRate) {
|
||||
// No change.
|
||||
return false;
|
||||
return true;
|
||||
} else if (newCodecOperatingRate == CODEC_OPERATING_RATE_UNSET) {
|
||||
// The only way to clear the operating rate is to instantiate a new codec instance. See
|
||||
// [Internal ref: b/111543954].
|
||||
drainAndReinitializeCodec();
|
||||
return true;
|
||||
return false;
|
||||
} else if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET
|
||||
|| newCodecOperatingRate > assumedMinimumCodecOperatingRate) {
|
||||
// We need to set the operating rate, either because we've set it previously or because it's
|
||||
@ -1635,36 +1665,48 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
codecParameters.putFloat(MediaFormat.KEY_OPERATING_RATE, newCodecOperatingRate);
|
||||
codec.setParameters(codecParameters);
|
||||
codecOperatingRate = newCodecOperatingRate;
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Starts draining the codec for flush. */
|
||||
private void drainAndFlushCodec() {
|
||||
/**
|
||||
* Starts draining the codec for a flush, or to release and re-initialize the codec if flushing
|
||||
* will not be possible. If no buffers have been queued to the codec then this method is a no-op.
|
||||
*
|
||||
* @return False if codec release and re-initialization was triggered due to the need to apply a
|
||||
* flush workaround. True in all other cases.
|
||||
*/
|
||||
private boolean drainAndFlushCodec() {
|
||||
if (codecReceivedBuffers) {
|
||||
codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM;
|
||||
if (codecNeedsFlushWorkaround || codecNeedsEosFlushWorkaround) {
|
||||
codecDrainAction = DRAIN_ACTION_REINITIALIZE;
|
||||
return false;
|
||||
} else {
|
||||
codecDrainAction = DRAIN_ACTION_FLUSH;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts draining the codec to update its DRM session. The update may occur immediately if no
|
||||
* buffers have been queued to the codec.
|
||||
* Starts draining the codec to flush it and update its DRM session, or to release and
|
||||
* re-initialize the codec if flushing will not be possible. If no buffers have been queued to the
|
||||
* codec then this method updates the DRM session immediately without flushing the codec.
|
||||
*
|
||||
* @throws ExoPlaybackException If an error occurs updating the codec's DRM session.
|
||||
* @return False if codec release and re-initialization was triggered due to the need to apply a
|
||||
* flush workaround. True in all other cases.
|
||||
*/
|
||||
@TargetApi(23) // Only called when SDK_INT >= 23, but lint isn't clever enough to know.
|
||||
private void drainAndUpdateCodecDrmSessionV23() throws ExoPlaybackException {
|
||||
private boolean drainAndUpdateCodecDrmSessionV23() throws ExoPlaybackException {
|
||||
if (codecReceivedBuffers) {
|
||||
codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM;
|
||||
if (codecNeedsFlushWorkaround || codecNeedsEosFlushWorkaround) {
|
||||
codecDrainAction = DRAIN_ACTION_REINITIALIZE;
|
||||
return false;
|
||||
} else {
|
||||
codecDrainAction = DRAIN_ACTION_FLUSH_AND_UPDATE_DRM_SESSION;
|
||||
}
|
||||
@ -1672,6 +1714,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
// Nothing has been queued to the decoder, so we can do the update immediately.
|
||||
updateDrmSessionV23();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -34,6 +34,7 @@ import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.analytics.AnalyticsListener;
|
||||
import com.google.android.exoplayer2.audio.AudioAttributes;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.source.LoadEventInfo;
|
||||
import com.google.android.exoplayer2.source.MediaLoadData;
|
||||
@ -328,7 +329,8 @@ public class EventLogger implements AnalyticsListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioInputFormatChanged(EventTime eventTime, Format format) {
|
||||
public void onAudioInputFormatChanged(
|
||||
EventTime eventTime, Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||
logd(eventTime, "audioInputFormat", Format.toLogString(format));
|
||||
}
|
||||
|
||||
@ -393,7 +395,8 @@ public class EventLogger implements AnalyticsListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoInputFormatChanged(EventTime eventTime, Format format) {
|
||||
public void onVideoInputFormatChanged(
|
||||
EventTime eventTime, Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||
logd(eventTime, "videoInputFormat", Format.toLogString(format));
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,9 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.video;
|
||||
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_DRM_SESSION_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_REUSE_NOT_IMPLEMENTED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import android.os.Handler;
|
||||
@ -34,6 +37,7 @@ import com.google.android.exoplayer2.decoder.Decoder;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderException;
|
||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.drm.DrmSession;
|
||||
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
|
||||
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||
@ -97,9 +101,12 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
||||
|
||||
private Format inputFormat;
|
||||
private Format outputFormat;
|
||||
|
||||
@Nullable
|
||||
private Decoder<
|
||||
VideoDecoderInputBuffer, ? extends VideoDecoderOutputBuffer, ? extends DecoderException>
|
||||
decoder;
|
||||
|
||||
private VideoDecoderInputBuffer inputBuffer;
|
||||
private VideoDecoderOutputBuffer outputBuffer;
|
||||
@Nullable private Surface surface;
|
||||
@ -366,7 +373,24 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
||||
|
||||
if (decoder == null) {
|
||||
maybeInitDecoder();
|
||||
} else if (sourceDrmSession != decoderDrmSession || !canKeepCodec(oldFormat, inputFormat)) {
|
||||
eventDispatcher.inputFormatChanged(inputFormat, /* decoderReuseEvaluation= */ null);
|
||||
return;
|
||||
}
|
||||
|
||||
DecoderReuseEvaluation evaluation;
|
||||
if (sourceDrmSession != decoderDrmSession) {
|
||||
evaluation =
|
||||
new DecoderReuseEvaluation(
|
||||
decoder.getName(),
|
||||
oldFormat,
|
||||
newFormat,
|
||||
REUSE_RESULT_NO,
|
||||
DISCARD_REASON_DRM_SESSION_CHANGED);
|
||||
} else {
|
||||
evaluation = canReuseDecoder(decoder.getName(), oldFormat, newFormat);
|
||||
}
|
||||
|
||||
if (evaluation.result == REUSE_RESULT_NO) {
|
||||
if (decoderReceivedBuffers) {
|
||||
// Signal end of stream and wait for any final output buffers before re-initialization.
|
||||
decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM;
|
||||
@ -376,8 +400,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
||||
maybeInitDecoder();
|
||||
}
|
||||
}
|
||||
|
||||
eventDispatcher.inputFormatChanged(inputFormat);
|
||||
eventDispatcher.inputFormatChanged(inputFormat, evaluation);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -626,14 +649,18 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
||||
protected abstract void setDecoderOutputMode(@C.VideoOutputMode int outputMode);
|
||||
|
||||
/**
|
||||
* Returns whether the existing decoder can be kept for a new format.
|
||||
* Evaluates whether the existing decoder can be reused for a new {@link Format}.
|
||||
*
|
||||
* <p>The default implementation does not allow decoder reuse.
|
||||
*
|
||||
* @param oldFormat The previous format.
|
||||
* @param newFormat The new format.
|
||||
* @return Whether the existing decoder can be kept.
|
||||
* @return The result of the evaluation.
|
||||
*/
|
||||
protected boolean canKeepCodec(Format oldFormat, Format newFormat) {
|
||||
return false;
|
||||
protected DecoderReuseEvaluation canReuseDecoder(
|
||||
String decoderName, Format oldFormat, Format newFormat) {
|
||||
return new DecoderReuseEvaluation(
|
||||
decoderName, oldFormat, newFormat, REUSE_RESULT_NO, DISCARD_REASON_REUSE_NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
// Internal methods.
|
||||
|
@ -15,7 +15,9 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.video;
|
||||
|
||||
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_NO;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_MAX_RESOLUTION_EXCEEDED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
@ -46,11 +48,12 @@ import com.google.android.exoplayer2.PlayerMessage.Target;
|
||||
import com.google.android.exoplayer2.RendererCapabilities;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DecoderDiscardReasons;
|
||||
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecDecoderException;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KeepCodecResult;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
|
||||
@ -566,15 +569,24 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
@KeepCodecResult
|
||||
protected int canKeepCodec(
|
||||
MediaCodecAdapter codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
|
||||
if (newFormat.width > codecMaxValues.width
|
||||
|| newFormat.height > codecMaxValues.height
|
||||
|| getMaxInputSize(codecInfo, newFormat) > codecMaxValues.inputSize) {
|
||||
return KEEP_CODEC_RESULT_NO;
|
||||
protected DecoderReuseEvaluation canReuseCodec(
|
||||
MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
|
||||
DecoderReuseEvaluation evaluation = codecInfo.canReuseCodec(oldFormat, newFormat);
|
||||
|
||||
@DecoderDiscardReasons int discardReasons = evaluation.discardReasons;
|
||||
if (newFormat.width > codecMaxValues.width || newFormat.height > codecMaxValues.height) {
|
||||
discardReasons |= DISCARD_REASON_VIDEO_MAX_RESOLUTION_EXCEEDED;
|
||||
}
|
||||
return codecInfo.canKeepCodec(oldFormat, newFormat);
|
||||
if (getMaxInputSize(codecInfo, newFormat) > codecMaxValues.inputSize) {
|
||||
discardReasons |= DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED;
|
||||
}
|
||||
|
||||
return new DecoderReuseEvaluation(
|
||||
codecInfo.name,
|
||||
oldFormat,
|
||||
newFormat,
|
||||
discardReasons != 0 ? REUSE_RESULT_NO : evaluation.result,
|
||||
discardReasons);
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@ -621,9 +633,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
|
||||
super.onInputFormatChanged(formatHolder);
|
||||
eventDispatcher.inputFormatChanged(formatHolder.format);
|
||||
@Nullable
|
||||
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
|
||||
throws ExoPlaybackException {
|
||||
@Nullable DecoderReuseEvaluation evaluation = super.onInputFormatChanged(formatHolder);
|
||||
eventDispatcher.inputFormatChanged(formatHolder.format, evaluation);
|
||||
return evaluation;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1333,7 +1348,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
// from format to avoid codec re-use being ruled out for only this reason.
|
||||
streamFormat = streamFormat.buildUpon().setColorInfo(format.colorInfo).build();
|
||||
}
|
||||
if (codecInfo.canKeepCodec(format, streamFormat) != KEEP_CODEC_RESULT_NO) {
|
||||
if (codecInfo.canReuseCodec(format, streamFormat).result != REUSE_RESULT_NO) {
|
||||
haveUnknownDimensions |=
|
||||
(streamFormat.width == Format.NO_VALUE || streamFormat.height == Format.NO_VALUE);
|
||||
maxWidth = max(maxWidth, streamFormat.width);
|
||||
|
@ -25,6 +25,7 @@ import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.Renderer;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
|
||||
/**
|
||||
@ -52,12 +53,23 @@ public interface VideoRendererEventListener {
|
||||
default void onVideoDecoderInitialized(
|
||||
String decoderName, long initializedTimestampMs, long initializationDurationMs) {}
|
||||
|
||||
/** @deprecated Use {@link #onVideoInputFormatChanged(Format, DecoderReuseEvaluation)}. */
|
||||
@Deprecated
|
||||
default void onVideoInputFormatChanged(Format format) {}
|
||||
|
||||
/**
|
||||
* Called when the format of the media being consumed by the renderer changes.
|
||||
*
|
||||
* @param format The new format.
|
||||
* @param decoderReuseEvaluation The result of the evaluation to determine whether an existing
|
||||
* decoder instance can be reused for the new format, or {@code null} if the renderer did not
|
||||
* have a decoder.
|
||||
*/
|
||||
default void onVideoInputFormatChanged(Format format) {}
|
||||
@SuppressWarnings("deprecation")
|
||||
default void onVideoInputFormatChanged(
|
||||
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||
onVideoInputFormatChanged(format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to report the number of frames dropped by the renderer. Dropped frames are reported
|
||||
@ -172,10 +184,15 @@ public interface VideoRendererEventListener {
|
||||
}
|
||||
}
|
||||
|
||||
/** Invokes {@link VideoRendererEventListener#onVideoInputFormatChanged(Format)}. */
|
||||
public void inputFormatChanged(Format format) {
|
||||
/**
|
||||
* Invokes {@link VideoRendererEventListener#onVideoInputFormatChanged(Format,
|
||||
* DecoderReuseEvaluation)}.
|
||||
*/
|
||||
public void inputFormatChanged(
|
||||
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||
if (handler != null) {
|
||||
handler.post(() -> castNonNull(listener).onVideoInputFormatChanged(format));
|
||||
handler.post(
|
||||
() -> castNonNull(listener).onVideoInputFormatChanged(format, decoderReuseEvaluation));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,9 +15,15 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.mediacodec;
|
||||
|
||||
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_NO;
|
||||
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_YES_WITH_FLUSH;
|
||||
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_INITIALIZATION_DATA_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_MIME_TYPE_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_RESOLUTION_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_ROTATION_CHANGED;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITH_FLUSH;
|
||||
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITH_RECONFIGURATION;
|
||||
import static com.google.android.exoplayer2.util.MimeTypes.AUDIO_AAC;
|
||||
import static com.google.android.exoplayer2.util.MimeTypes.VIDEO_AV1;
|
||||
import static com.google.android.exoplayer2.util.MimeTypes.VIDEO_H264;
|
||||
@ -26,6 +32,7 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.video.ColorInfo;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.junit.Test;
|
||||
@ -71,7 +78,14 @@ public final class MediaCodecInfoTest {
|
||||
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true);
|
||||
|
||||
Format hdAv1Format = FORMAT_H264_HD.buildUpon().setSampleMimeType(VIDEO_AV1).build();
|
||||
assertThat(codecInfo.canKeepCodec(FORMAT_H264_HD, hdAv1Format)).isEqualTo(KEEP_CODEC_RESULT_NO);
|
||||
assertThat(codecInfo.canReuseCodec(FORMAT_H264_HD, hdAv1Format))
|
||||
.isEqualTo(
|
||||
new DecoderReuseEvaluation(
|
||||
codecInfo.name,
|
||||
FORMAT_H264_HD,
|
||||
hdAv1Format,
|
||||
REUSE_RESULT_NO,
|
||||
DISCARD_REASON_MIME_TYPE_CHANGED));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -79,24 +93,42 @@ public final class MediaCodecInfoTest {
|
||||
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true);
|
||||
|
||||
Format hdRotatedFormat = FORMAT_H264_HD.buildUpon().setRotationDegrees(90).build();
|
||||
assertThat(codecInfo.canKeepCodec(FORMAT_H264_HD, hdRotatedFormat))
|
||||
.isEqualTo(KEEP_CODEC_RESULT_NO);
|
||||
assertThat(codecInfo.canReuseCodec(FORMAT_H264_HD, hdRotatedFormat))
|
||||
.isEqualTo(
|
||||
new DecoderReuseEvaluation(
|
||||
codecInfo.name,
|
||||
FORMAT_H264_HD,
|
||||
hdRotatedFormat,
|
||||
REUSE_RESULT_NO,
|
||||
DISCARD_REASON_VIDEO_ROTATION_CHANGED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canKeepCodec_withResolutionChange_adaptiveCodec_returnsYesWithReconfiguration() {
|
||||
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true);
|
||||
|
||||
assertThat(codecInfo.canKeepCodec(FORMAT_H264_HD, FORMAT_H264_4K))
|
||||
.isEqualTo(KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION);
|
||||
assertThat(codecInfo.canReuseCodec(FORMAT_H264_HD, FORMAT_H264_4K))
|
||||
.isEqualTo(
|
||||
new DecoderReuseEvaluation(
|
||||
codecInfo.name,
|
||||
FORMAT_H264_HD,
|
||||
FORMAT_H264_4K,
|
||||
REUSE_RESULT_YES_WITH_RECONFIGURATION,
|
||||
/* discardReasons= */ 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canKeepCodec_withResolutionChange_nonAdaptiveCodec_returnsNo() {
|
||||
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ false);
|
||||
|
||||
assertThat(codecInfo.canKeepCodec(FORMAT_H264_HD, FORMAT_H264_4K))
|
||||
.isEqualTo(KEEP_CODEC_RESULT_NO);
|
||||
assertThat(codecInfo.canReuseCodec(FORMAT_H264_HD, FORMAT_H264_4K))
|
||||
.isEqualTo(
|
||||
new DecoderReuseEvaluation(
|
||||
codecInfo.name,
|
||||
FORMAT_H264_HD,
|
||||
FORMAT_H264_4K,
|
||||
REUSE_RESULT_NO,
|
||||
DISCARD_REASON_VIDEO_RESOLUTION_CHANGED));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -105,8 +137,14 @@ public final class MediaCodecInfoTest {
|
||||
|
||||
Format hdVariantFormat =
|
||||
FORMAT_H264_HD.buildUpon().setInitializationData(ImmutableList.of(new byte[] {0})).build();
|
||||
assertThat(codecInfo.canKeepCodec(FORMAT_H264_HD, hdVariantFormat))
|
||||
.isEqualTo(KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION);
|
||||
assertThat(codecInfo.canReuseCodec(FORMAT_H264_HD, hdVariantFormat))
|
||||
.isEqualTo(
|
||||
new DecoderReuseEvaluation(
|
||||
codecInfo.name,
|
||||
FORMAT_H264_HD,
|
||||
hdVariantFormat,
|
||||
REUSE_RESULT_YES_WITH_RECONFIGURATION,
|
||||
/* discardReasons= */ 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -115,8 +153,14 @@ public final class MediaCodecInfoTest {
|
||||
|
||||
Format hdrVariantFormat =
|
||||
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT601)).build();
|
||||
assertThat(codecInfo.canKeepCodec(hdrVariantFormat, FORMAT_H264_4K))
|
||||
.isEqualTo(KEEP_CODEC_RESULT_NO);
|
||||
assertThat(codecInfo.canReuseCodec(hdrVariantFormat, FORMAT_H264_4K))
|
||||
.isEqualTo(
|
||||
new DecoderReuseEvaluation(
|
||||
codecInfo.name,
|
||||
hdrVariantFormat,
|
||||
FORMAT_H264_4K,
|
||||
REUSE_RESULT_NO,
|
||||
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -125,8 +169,14 @@ public final class MediaCodecInfoTest {
|
||||
|
||||
Format hdrVariantFormat =
|
||||
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT601)).build();
|
||||
assertThat(codecInfo.canKeepCodec(FORMAT_H264_4K, hdrVariantFormat))
|
||||
.isEqualTo(KEEP_CODEC_RESULT_NO);
|
||||
assertThat(codecInfo.canReuseCodec(FORMAT_H264_4K, hdrVariantFormat))
|
||||
.isEqualTo(
|
||||
new DecoderReuseEvaluation(
|
||||
codecInfo.name,
|
||||
FORMAT_H264_4K,
|
||||
hdrVariantFormat,
|
||||
REUSE_RESULT_NO,
|
||||
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -137,18 +187,28 @@ public final class MediaCodecInfoTest {
|
||||
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT601)).build();
|
||||
Format hdrVariantFormat2 =
|
||||
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT709)).build();
|
||||
assertThat(codecInfo.canKeepCodec(hdrVariantFormat1, hdrVariantFormat2))
|
||||
.isEqualTo(KEEP_CODEC_RESULT_NO);
|
||||
assertThat(codecInfo.canKeepCodec(hdrVariantFormat1, hdrVariantFormat2))
|
||||
.isEqualTo(KEEP_CODEC_RESULT_NO);
|
||||
assertThat(codecInfo.canReuseCodec(hdrVariantFormat1, hdrVariantFormat2))
|
||||
.isEqualTo(
|
||||
new DecoderReuseEvaluation(
|
||||
codecInfo.name,
|
||||
hdrVariantFormat1,
|
||||
hdrVariantFormat2,
|
||||
REUSE_RESULT_NO,
|
||||
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canKeepCodec_audioWithDifferentChannelCounts_returnsNo() {
|
||||
MediaCodecInfo codecInfo = buildAacCodecInfo();
|
||||
|
||||
assertThat(codecInfo.canKeepCodec(FORMAT_AAC_STEREO, FORMAT_AAC_SURROUND))
|
||||
.isEqualTo(KEEP_CODEC_RESULT_NO);
|
||||
assertThat(codecInfo.canReuseCodec(FORMAT_AAC_STEREO, FORMAT_AAC_SURROUND))
|
||||
.isEqualTo(
|
||||
new DecoderReuseEvaluation(
|
||||
codecInfo.name,
|
||||
FORMAT_AAC_STEREO,
|
||||
FORMAT_AAC_SURROUND,
|
||||
REUSE_RESULT_NO,
|
||||
DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -156,8 +216,14 @@ public final class MediaCodecInfoTest {
|
||||
MediaCodecInfo codecInfo = buildAacCodecInfo();
|
||||
|
||||
Format stereoVariantFormat = FORMAT_AAC_STEREO.buildUpon().setAverageBitrate(100).build();
|
||||
assertThat(codecInfo.canKeepCodec(FORMAT_AAC_STEREO, stereoVariantFormat))
|
||||
.isEqualTo(KEEP_CODEC_RESULT_YES_WITH_FLUSH);
|
||||
assertThat(codecInfo.canReuseCodec(FORMAT_AAC_STEREO, stereoVariantFormat))
|
||||
.isEqualTo(
|
||||
new DecoderReuseEvaluation(
|
||||
codecInfo.name,
|
||||
FORMAT_AAC_STEREO,
|
||||
stereoVariantFormat,
|
||||
REUSE_RESULT_YES_WITH_FLUSH,
|
||||
/* discardReasons= */ 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -169,8 +235,14 @@ public final class MediaCodecInfoTest {
|
||||
.buildUpon()
|
||||
.setInitializationData(ImmutableList.of(new byte[] {0}))
|
||||
.build();
|
||||
assertThat(codecInfo.canKeepCodec(FORMAT_AAC_STEREO, stereoVariantFormat))
|
||||
.isEqualTo(KEEP_CODEC_RESULT_NO);
|
||||
assertThat(codecInfo.canReuseCodec(FORMAT_AAC_STEREO, stereoVariantFormat))
|
||||
.isEqualTo(
|
||||
new DecoderReuseEvaluation(
|
||||
codecInfo.name,
|
||||
FORMAT_AAC_STEREO,
|
||||
stereoVariantFormat,
|
||||
REUSE_RESULT_NO,
|
||||
DISCARD_REASON_INITIALIZATION_DATA_CHANGED));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -30,6 +30,7 @@ import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.FormatHolder;
|
||||
import com.google.android.exoplayer2.Renderer;
|
||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
|
||||
@ -165,12 +166,15 @@ import java.util.ArrayList;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
|
||||
super.onInputFormatChanged(formatHolder);
|
||||
@Nullable
|
||||
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
|
||||
throws ExoPlaybackException {
|
||||
@Nullable DecoderReuseEvaluation evaluation = super.onInputFormatChanged(formatHolder);
|
||||
// Ensure timestamps of buffers queued after this format change are never inserted into the
|
||||
// queue of expected output timestamps before those of buffers that have already been queued.
|
||||
minimumInsertIndex = startIndex + queueSize;
|
||||
inputFormatChanged = true;
|
||||
return evaluation;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -55,7 +55,7 @@ public class FakeAudioRenderer extends FakeRenderer {
|
||||
|
||||
@Override
|
||||
protected void onFormatChanged(Format format) {
|
||||
eventDispatcher.inputFormatChanged(format);
|
||||
eventDispatcher.inputFormatChanged(format, /* decoderReuseEvaluation= */ null);
|
||||
eventDispatcher.decoderInitialized(
|
||||
/* decoderName= */ "fake.audio.decoder",
|
||||
/* initializedTimestampMs= */ SystemClock.elapsedRealtime(),
|
||||
|
@ -85,7 +85,7 @@ public class FakeVideoRenderer extends FakeRenderer {
|
||||
|
||||
@Override
|
||||
protected void onFormatChanged(Format format) {
|
||||
eventDispatcher.inputFormatChanged(format);
|
||||
eventDispatcher.inputFormatChanged(format, /* decoderReuseEvaluation= */ null);
|
||||
eventDispatcher.decoderInitialized(
|
||||
/* decoderName= */ "fake.video.decoder",
|
||||
/* initializedTimestampMs= */ SystemClock.elapsedRealtime(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user