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:
|
* DRM:
|
||||||
* Fix playback failure when switching from PlayReady protected content to
|
* Fix playback failure when switching from PlayReady protected content to
|
||||||
Widevine or Clearkey protected content in a playlist.
|
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:
|
* IMA extension:
|
||||||
* Upgrade IMA SDK dependency to 3.21.0, and release the `AdsLoader`
|
* Upgrade IMA SDK dependency to 3.21.0, and release the `AdsLoader`
|
||||||
([#7344](https://github.com/google/ExoPlayer/issues/7344)).
|
([#7344](https://github.com/google/ExoPlayer/issues/7344)).
|
||||||
|
@ -15,12 +15,15 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.ext.av1;
|
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.os.Handler;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.RendererCapabilities;
|
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.drm.ExoMediaCrypto;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.TraceUtil;
|
import com.google.android.exoplayer2.util.TraceUtil;
|
||||||
@ -164,7 +167,13 @@ public class Libgav1VideoRenderer extends DecoderVideoRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canKeepCodec(Format oldFormat, Format newFormat) {
|
protected DecoderReuseEvaluation canReuseDecoder(
|
||||||
return true;
|
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;
|
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.os.Handler;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import androidx.annotation.Nullable;
|
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.Format;
|
||||||
import com.google.android.exoplayer2.RendererCapabilities;
|
import com.google.android.exoplayer2.RendererCapabilities;
|
||||||
import com.google.android.exoplayer2.decoder.Decoder;
|
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.drm.ExoMediaCrypto;
|
||||||
import com.google.android.exoplayer2.util.TraceUtil;
|
import com.google.android.exoplayer2.util.TraceUtil;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
@ -116,7 +121,15 @@ public final class FfmpegVideoRenderer extends DecoderVideoRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canKeepCodec(Format oldFormat, Format newFormat) {
|
protected DecoderReuseEvaluation canReuseDecoder(
|
||||||
return Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType);
|
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;
|
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 static java.lang.Runtime.getRuntime;
|
||||||
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@ -23,6 +24,7 @@ import androidx.annotation.Nullable;
|
|||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.RendererCapabilities;
|
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.drm.ExoMediaCrypto;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.TraceUtil;
|
import com.google.android.exoplayer2.util.TraceUtil;
|
||||||
@ -169,7 +171,13 @@ public class LibvpxVideoRenderer extends DecoderVideoRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canKeepCodec(Format oldFormat, Format newFormat) {
|
protected DecoderReuseEvaluation canReuseDecoder(
|
||||||
return true;
|
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.AudioRendererEventListener;
|
||||||
import com.google.android.exoplayer2.audio.AuxEffectInfo;
|
import com.google.android.exoplayer2.audio.AuxEffectInfo;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
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.DeviceInfo;
|
||||||
import com.google.android.exoplayer2.device.DeviceListener;
|
import com.google.android.exoplayer2.device.DeviceListener;
|
||||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||||
@ -2242,10 +2243,11 @@ public class SimpleExoPlayer extends BasePlayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onVideoInputFormatChanged(Format format) {
|
public void onVideoInputFormatChanged(
|
||||||
|
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||||
videoFormat = format;
|
videoFormat = format;
|
||||||
for (VideoRendererEventListener videoDebugListener : videoDebugListeners) {
|
for (VideoRendererEventListener videoDebugListener : videoDebugListeners) {
|
||||||
videoDebugListener.onVideoInputFormatChanged(format);
|
videoDebugListener.onVideoInputFormatChanged(format, decoderReuseEvaluation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2337,10 +2339,11 @@ public class SimpleExoPlayer extends BasePlayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAudioInputFormatChanged(Format format) {
|
public void onAudioInputFormatChanged(
|
||||||
|
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||||
audioFormat = format;
|
audioFormat = format;
|
||||||
for (AudioRendererEventListener audioDebugListener : audioDebugListeners) {
|
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.AudioListener;
|
||||||
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
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.drm.DrmSessionEventListener;
|
||||||
import com.google.android.exoplayer2.metadata.Metadata;
|
import com.google.android.exoplayer2.metadata.Metadata;
|
||||||
import com.google.android.exoplayer2.metadata.MetadataOutput;
|
import com.google.android.exoplayer2.metadata.MetadataOutput;
|
||||||
@ -195,11 +196,12 @@ public class AnalyticsCollector
|
|||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
public final void onAudioInputFormatChanged(Format format) {
|
public final void onAudioInputFormatChanged(
|
||||||
|
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||||
listeners.sendEvent(
|
listeners.sendEvent(
|
||||||
listener -> {
|
listener -> {
|
||||||
listener.onAudioInputFormatChanged(eventTime, format);
|
listener.onAudioInputFormatChanged(eventTime, format, decoderReuseEvaluation);
|
||||||
listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_AUDIO, format);
|
listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_AUDIO, format);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -298,11 +300,12 @@ public class AnalyticsCollector
|
|||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
public final void onVideoInputFormatChanged(Format format) {
|
public final void onVideoInputFormatChanged(
|
||||||
|
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||||
listeners.sendEvent(
|
listeners.sendEvent(
|
||||||
listener -> {
|
listener -> {
|
||||||
listener.onVideoInputFormatChanged(eventTime, format);
|
listener.onVideoInputFormatChanged(eventTime, format, decoderReuseEvaluation);
|
||||||
listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_VIDEO, format);
|
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.AudioAttributes;
|
||||||
import com.google.android.exoplayer2.audio.AudioSink;
|
import com.google.android.exoplayer2.audio.AudioSink;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
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.metadata.Metadata;
|
||||||
import com.google.android.exoplayer2.source.LoadEventInfo;
|
import com.google.android.exoplayer2.source.LoadEventInfo;
|
||||||
import com.google.android.exoplayer2.source.MediaLoadData;
|
import com.google.android.exoplayer2.source.MediaLoadData;
|
||||||
@ -452,8 +453,8 @@ public interface AnalyticsListener {
|
|||||||
EventTime eventTime, int trackType, String decoderName, long initializationDurationMs) {}
|
EventTime eventTime, int trackType, String decoderName, long initializationDurationMs) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link #onAudioInputFormatChanged} and {@link #onVideoInputFormatChanged}
|
* @deprecated Use {@link #onAudioInputFormatChanged(EventTime, Format, DecoderReuseEvaluation)}
|
||||||
* instead.
|
* and {@link #onVideoInputFormatChanged(EventTime, Format, DecoderReuseEvaluation)}. instead.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
default void onDecoderInputFormatChanged(EventTime eventTime, int trackType, Format format) {}
|
default void onDecoderInputFormatChanged(EventTime eventTime, int trackType, Format format) {}
|
||||||
@ -482,13 +483,26 @@ public interface AnalyticsListener {
|
|||||||
default void onAudioDecoderInitialized(
|
default void onAudioDecoderInitialized(
|
||||||
EventTime eventTime, String decoderName, long initializationDurationMs) {}
|
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.
|
* Called when the format of the media being consumed by an audio renderer changes.
|
||||||
*
|
*
|
||||||
* @param eventTime The event time.
|
* @param eventTime The event time.
|
||||||
* @param format The new format.
|
* @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
|
* 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(
|
default void onVideoDecoderInitialized(
|
||||||
EventTime eventTime, String decoderName, long initializationDurationMs) {}
|
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.
|
* Called when the format of the media being consumed by a video renderer changes.
|
||||||
*
|
*
|
||||||
* @param eventTime The event time.
|
* @param eventTime The event time.
|
||||||
* @param format The new format.
|
* @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.
|
* 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.Player;
|
||||||
import com.google.android.exoplayer2.Renderer;
|
import com.google.android.exoplayer2.Renderer;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
|
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,12 +62,23 @@ public interface AudioRendererEventListener {
|
|||||||
default void onAudioDecoderInitialized(
|
default void onAudioDecoderInitialized(
|
||||||
String decoderName, long initializedTimestampMs, long initializationDurationMs) {}
|
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.
|
* Called when the format of the media being consumed by the renderer changes.
|
||||||
*
|
*
|
||||||
* @param format The new format.
|
* @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
|
* 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)}. */
|
/** Invokes {@link AudioRendererEventListener#onAudioInputFormatChanged(Format)}. */
|
||||||
public void inputFormatChanged(Format format) {
|
public void inputFormatChanged(
|
||||||
|
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||||
if (handler != null) {
|
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;
|
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 static java.lang.Math.max;
|
||||||
|
|
||||||
import android.media.audiofx.Virtualizer;
|
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.DecoderCounters;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderException;
|
import com.google.android.exoplayer2.decoder.DecoderException;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
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.decoder.SimpleOutputBuffer;
|
||||||
import com.google.android.exoplayer2.drm.DrmSession;
|
import com.google.android.exoplayer2.drm.DrmSession;
|
||||||
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
|
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
|
||||||
@ -347,14 +351,19 @@ public abstract class DecoderAudioRenderer<
|
|||||||
protected abstract Format getOutputFormat(T decoder);
|
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 oldFormat The previous format.
|
||||||
* @param newFormat The new 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) {
|
protected DecoderReuseEvaluation canReuseDecoder(
|
||||||
return false;
|
String decoderName, Format oldFormat, Format newFormat) {
|
||||||
|
return new DecoderReuseEvaluation(
|
||||||
|
decoderName, oldFormat, newFormat, REUSE_RESULT_NO, DISCARD_REASON_REUSE_NOT_IMPLEMENTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean drainOutputBuffer()
|
private boolean drainOutputBuffer()
|
||||||
@ -655,10 +664,29 @@ public abstract class DecoderAudioRenderer<
|
|||||||
setSourceDrmSession(formatHolder.drmSession);
|
setSourceDrmSession(formatHolder.drmSession);
|
||||||
Format oldFormat = inputFormat;
|
Format oldFormat = inputFormat;
|
||||||
inputFormat = newFormat;
|
inputFormat = newFormat;
|
||||||
|
encoderDelay = newFormat.encoderDelay;
|
||||||
|
encoderPadding = newFormat.encoderPadding;
|
||||||
|
|
||||||
if (decoder == null) {
|
if (decoder == null) {
|
||||||
maybeInitDecoder();
|
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) {
|
if (decoderReceivedBuffers) {
|
||||||
// Signal end of stream and wait for any final output buffers before re-initialization.
|
// Signal end of stream and wait for any final output buffers before re-initialization.
|
||||||
decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM;
|
decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM;
|
||||||
@ -669,10 +697,7 @@ public abstract class DecoderAudioRenderer<
|
|||||||
audioTrackNeedsConfigure = true;
|
audioTrackNeedsConfigure = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
eventDispatcher.inputFormatChanged(inputFormat, evaluation);
|
||||||
encoderDelay = inputFormat.encoderDelay;
|
|
||||||
encoderPadding = inputFormat.encoderPadding;
|
|
||||||
eventDispatcher.inputFormatChanged(inputFormat);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onQueueInputBuffer(DecoderInputBuffer buffer) {
|
private void onQueueInputBuffer(DecoderInputBuffer buffer) {
|
||||||
|
@ -15,7 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.audio;
|
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 com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||||
import static java.lang.Math.max;
|
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.InitializationException;
|
||||||
import com.google.android.exoplayer2.audio.AudioSink.WriteException;
|
import com.google.android.exoplayer2.audio.AudioSink.WriteException;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
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.MediaCodecAdapter;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
|
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.MediaCodecRenderer;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
|
||||||
@ -328,13 +330,21 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@KeepCodecResult
|
protected DecoderReuseEvaluation canReuseCodec(
|
||||||
protected int canKeepCodec(
|
MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
|
||||||
MediaCodecAdapter codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
|
DecoderReuseEvaluation evaluation = codecInfo.canReuseCodec(oldFormat, newFormat);
|
||||||
|
|
||||||
|
@DecoderDiscardReasons int discardReasons = evaluation.discardReasons;
|
||||||
if (getCodecMaxInputSize(codecInfo, newFormat) > codecMaxInputSize) {
|
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
|
@Override
|
||||||
@ -370,9 +380,12 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
|
@Nullable
|
||||||
super.onInputFormatChanged(formatHolder);
|
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
|
||||||
eventDispatcher.inputFormatChanged(formatHolder.format);
|
throws ExoPlaybackException {
|
||||||
|
@Nullable DecoderReuseEvaluation evaluation = super.onInputFormatChanged(formatHolder);
|
||||||
|
eventDispatcher.inputFormatChanged(formatHolder.format, evaluation);
|
||||||
|
return evaluation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -664,7 +677,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
return maxInputSize;
|
return maxInputSize;
|
||||||
}
|
}
|
||||||
for (Format streamFormat : streamFormats) {
|
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));
|
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;
|
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.graphics.Point;
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaCodecInfo.AudioCapabilities;
|
import android.media.MediaCodecInfo.AudioCapabilities;
|
||||||
@ -22,18 +36,17 @@ import android.media.MediaCodecInfo.CodecCapabilities;
|
|||||||
import android.media.MediaCodecInfo.CodecProfileLevel;
|
import android.media.MediaCodecInfo.CodecProfileLevel;
|
||||||
import android.media.MediaCodecInfo.VideoCapabilities;
|
import android.media.MediaCodecInfo.VideoCapabilities;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import com.google.android.exoplayer2.Format;
|
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.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
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. */
|
/** Information about a {@link MediaCodec} for a given mime type. */
|
||||||
@SuppressWarnings("InlinedApi")
|
@SuppressWarnings("InlinedApi")
|
||||||
@ -47,28 +60,6 @@ public final class MediaCodecInfo {
|
|||||||
*/
|
*/
|
||||||
public static final int MAX_SUPPORTED_INSTANCES_UNKNOWN = -1;
|
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.
|
* The name of the decoder.
|
||||||
*
|
*
|
||||||
@ -361,7 +352,7 @@ public final class MediaCodecInfo {
|
|||||||
* @param isNewFormatComplete Whether {@code newFormat} is populated with format-specific
|
* @param isNewFormatComplete Whether {@code newFormat} is populated with format-specific
|
||||||
* metadata.
|
* metadata.
|
||||||
* @return Whether it is possible to adapt the decoder seamlessly.
|
* @return Whether it is possible to adapt the decoder seamlessly.
|
||||||
* @deprecated Use {@link #canKeepCodec}.
|
* @deprecated Use {@link #canReuseCodec}.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public boolean isSeamlessAdaptationSupported(
|
public boolean isSeamlessAdaptationSupported(
|
||||||
@ -369,49 +360,68 @@ public final class MediaCodecInfo {
|
|||||||
if (!isNewFormatComplete && oldFormat.colorInfo != null && newFormat.colorInfo == null) {
|
if (!isNewFormatComplete && oldFormat.colorInfo != null && newFormat.colorInfo == null) {
|
||||||
newFormat = newFormat.buildUpon().setColorInfo(oldFormat.colorInfo).build();
|
newFormat = newFormat.buildUpon().setColorInfo(oldFormat.colorInfo).build();
|
||||||
}
|
}
|
||||||
@KeepCodecResult int keepCodecResult = canKeepCodec(oldFormat, newFormat);
|
@DecoderReuseResult int reuseResult = canReuseCodec(oldFormat, newFormat).result;
|
||||||
return keepCodecResult == KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION
|
return reuseResult == REUSE_RESULT_YES_WITH_RECONFIGURATION
|
||||||
|| keepCodecResult == KEEP_CODEC_RESULT_YES_WITHOUT_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
|
* Evaluates whether it's possible to reuse an instance of this decoder that's currently decoding
|
||||||
* decoding {@code oldFormat} to decode {@code newFormat} instead.
|
* {@code oldFormat} to decode {@code newFormat} instead.
|
||||||
*
|
*
|
||||||
* <p>For adaptation to succeed, the codec must also be configured with maximum values that are
|
* <p>For adaptation to succeed, the codec must also be configured with maximum values that are
|
||||||
* compatible with the new format.
|
* compatible with the new format.
|
||||||
*
|
*
|
||||||
* @param oldFormat The format being decoded.
|
* @param oldFormat The format being decoded.
|
||||||
* @param newFormat The new format.
|
* @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 DecoderReuseEvaluation canReuseCodec(Format oldFormat, Format newFormat) {
|
||||||
public int canKeepCodec(Format oldFormat, Format newFormat) {
|
@DecoderDiscardReasons int discardReasons = 0;
|
||||||
if (!Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType)) {
|
if (!Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType)) {
|
||||||
return KEEP_CODEC_RESULT_NO;
|
discardReasons |= DISCARD_REASON_MIME_TYPE_CHANGED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isVideo) {
|
if (isVideo) {
|
||||||
if (oldFormat.rotationDegrees == newFormat.rotationDegrees
|
if (oldFormat.rotationDegrees != newFormat.rotationDegrees) {
|
||||||
&& (adaptive
|
discardReasons |= DISCARD_REASON_VIDEO_ROTATION_CHANGED;
|
||||||
|| (oldFormat.width == newFormat.width && oldFormat.height == newFormat.height))
|
}
|
||||||
&& Util.areEqual(oldFormat.colorInfo, newFormat.colorInfo)) {
|
if (!adaptive
|
||||||
if (oldFormat.initializationDataEquals(newFormat)) {
|
&& (oldFormat.width != newFormat.width || oldFormat.height != newFormat.height)) {
|
||||||
return KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION;
|
discardReasons |= DISCARD_REASON_VIDEO_RESOLUTION_CHANGED;
|
||||||
} else if (!needsAdaptationReconfigureWorkaround(name)) {
|
}
|
||||||
return KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION;
|
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 {
|
} else {
|
||||||
if (oldFormat.channelCount != newFormat.channelCount
|
if (oldFormat.channelCount != newFormat.channelCount) {
|
||||||
|| oldFormat.sampleRate != newFormat.sampleRate
|
discardReasons |= DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED;
|
||||||
|| oldFormat.pcmEncoding != newFormat.pcmEncoding) {
|
}
|
||||||
return KEEP_CODEC_RESULT_NO;
|
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
|
// Check whether we're adapting between two xHE-AAC formats, for which adaptation is possible
|
||||||
// without reconfiguration or flushing.
|
// without reconfiguration or flushing.
|
||||||
if (MimeTypes.AUDIO_AAC.equals(mimeType)) {
|
if (discardReasons == 0 && MimeTypes.AUDIO_AAC.equals(mimeType)) {
|
||||||
@Nullable
|
@Nullable
|
||||||
Pair<Integer, Integer> oldCodecProfileLevel =
|
Pair<Integer, Integer> oldCodecProfileLevel =
|
||||||
MediaCodecUtil.getCodecProfileAndLevel(oldFormat);
|
MediaCodecUtil.getCodecProfileAndLevel(oldFormat);
|
||||||
@ -423,18 +433,30 @@ public final class MediaCodecInfo {
|
|||||||
int newProfile = newCodecProfileLevel.first;
|
int newProfile = newCodecProfileLevel.first;
|
||||||
if (oldProfile == CodecProfileLevel.AACObjectXHE
|
if (oldProfile == CodecProfileLevel.AACObjectXHE
|
||||||
&& newProfile == 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)
|
if (!oldFormat.initializationDataEquals(newFormat)) {
|
||||||
&& !needsAdaptationFlushWorkaround(mimeType)) {
|
discardReasons |= DISCARD_REASON_INITIALIZATION_DATA_CHANGED;
|
||||||
return KEEP_CODEC_RESULT_YES_WITH_FLUSH;
|
}
|
||||||
|
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;
|
package com.google.android.exoplayer2.mediacodec;
|
||||||
|
|
||||||
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_NO;
|
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_DRM_SESSION_CHANGED;
|
||||||
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION;
|
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_OPERATING_RATE_CHANGED;
|
||||||
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_YES_WITH_FLUSH;
|
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_REUSE_NOT_IMPLEMENTED;
|
||||||
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION;
|
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.checkNotNull;
|
||||||
import static com.google.android.exoplayer2.util.Assertions.checkState;
|
import static com.google.android.exoplayer2.util.Assertions.checkState;
|
||||||
import static java.lang.Math.max;
|
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.FormatHolder;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
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;
|
||||||
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
|
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
|
||||||
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||||
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
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.mediacodec.MediaCodecUtil.DecoderQueryException;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
import com.google.android.exoplayer2.source.SampleStream;
|
import com.google.android.exoplayer2.source.SampleStream;
|
||||||
@ -710,7 +715,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
if (codec != null
|
if (codec != null
|
||||||
&& codecDrainAction != DRAIN_ACTION_REINITIALIZE
|
&& codecDrainAction != DRAIN_ACTION_REINITIALIZE
|
||||||
&& getState() != STATE_DISABLED) {
|
&& 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}.
|
* @param formatHolder A {@link FormatHolder} that holds the new {@link Format}.
|
||||||
* @throws ExoPlaybackException If an error occurs re-initializing the {@link MediaCodec}.
|
* @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
|
@CallSuper
|
||||||
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
|
@Nullable
|
||||||
|
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
|
||||||
|
throws ExoPlaybackException {
|
||||||
waitingForFirstSampleInFormat = true;
|
waitingForFirstSampleInFormat = true;
|
||||||
Format newFormat = checkNotNull(formatHolder.format);
|
Format newFormat = checkNotNull(formatHolder.format);
|
||||||
setSourceDrmSession(formatHolder.drmSession);
|
setSourceDrmSession(formatHolder.drmSession);
|
||||||
@ -1385,7 +1394,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
|
|
||||||
if (bypassEnabled) {
|
if (bypassEnabled) {
|
||||||
bypassDrainAndReinitialize = true;
|
bypassDrainAndReinitialize = true;
|
||||||
return; // Need to drain batch buffer first.
|
return null; // Need to drain batch buffer first.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (codec == null) {
|
if (codec == null) {
|
||||||
@ -1393,66 +1402,86 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
availableCodecInfos = null;
|
availableCodecInfos = null;
|
||||||
}
|
}
|
||||||
maybeInitCodecOrBypass();
|
maybeInitCodecOrBypass();
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have an existing codec that we may need to reconfigure, re-initialize, or release to
|
// 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
|
// switch to bypass. If the existing codec instance is kept then its operating rate and DRM
|
||||||
// session may need to be updated.
|
// session may need to be updated.
|
||||||
|
MediaCodecAdapter oldCodec = codec;
|
||||||
|
Format oldFormat = codecInputFormat;
|
||||||
if (drmNeedsCodecReinitialization(codecInfo, newFormat, codecDrmSession, sourceDrmSession)) {
|
if (drmNeedsCodecReinitialization(codecInfo, newFormat, codecDrmSession, sourceDrmSession)) {
|
||||||
drainAndReinitializeCodec();
|
drainAndReinitializeCodec();
|
||||||
return;
|
return new DecoderReuseEvaluation(
|
||||||
|
codecInfo.name,
|
||||||
|
oldFormat,
|
||||||
|
newFormat,
|
||||||
|
REUSE_RESULT_NO,
|
||||||
|
DISCARD_REASON_DRM_SESSION_CHANGED);
|
||||||
}
|
}
|
||||||
boolean drainAndUpdateCodecDrmSession = sourceDrmSession != codecDrmSession;
|
boolean drainAndUpdateCodecDrmSession = sourceDrmSession != codecDrmSession;
|
||||||
Assertions.checkState(!drainAndUpdateCodecDrmSession || Util.SDK_INT >= 23);
|
Assertions.checkState(!drainAndUpdateCodecDrmSession || Util.SDK_INT >= 23);
|
||||||
|
|
||||||
switch (canKeepCodec(codec, codecInfo, codecInputFormat, newFormat)) {
|
DecoderReuseEvaluation evaluation = canReuseCodec(codecInfo, oldFormat, newFormat);
|
||||||
case KEEP_CODEC_RESULT_NO:
|
@DecoderDiscardReasons int overridingDiscardReasons = 0;
|
||||||
|
switch (evaluation.result) {
|
||||||
|
case REUSE_RESULT_NO:
|
||||||
drainAndReinitializeCodec();
|
drainAndReinitializeCodec();
|
||||||
break;
|
break;
|
||||||
case KEEP_CODEC_RESULT_YES_WITH_FLUSH:
|
case REUSE_RESULT_YES_WITH_FLUSH:
|
||||||
if (updateOperatingRateOrReinitializeCodec(newFormat)) {
|
if (!updateCodecOperatingRate(newFormat)) {
|
||||||
// Codec re-initialization triggered.
|
overridingDiscardReasons |= DISCARD_REASON_OPERATING_RATE_CHANGED;
|
||||||
} else {
|
} else {
|
||||||
codecInputFormat = newFormat;
|
codecInputFormat = newFormat;
|
||||||
if (drainAndUpdateCodecDrmSession) {
|
if (drainAndUpdateCodecDrmSession) {
|
||||||
drainAndUpdateCodecDrmSessionV23();
|
if (!drainAndUpdateCodecDrmSessionV23()) {
|
||||||
} else {
|
overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
|
||||||
drainAndFlushCodec();
|
}
|
||||||
|
} else if (!drainAndFlushCodec()) {
|
||||||
|
overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION:
|
case REUSE_RESULT_YES_WITH_RECONFIGURATION:
|
||||||
if (updateOperatingRateOrReinitializeCodec(newFormat)) {
|
if (!updateCodecOperatingRate(newFormat)) {
|
||||||
// Codec re-initialization triggered.
|
overridingDiscardReasons |= DISCARD_REASON_OPERATING_RATE_CHANGED;
|
||||||
} else {
|
} else {
|
||||||
codecReconfigured = true;
|
codecReconfigured = true;
|
||||||
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
|
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
|
||||||
codecNeedsAdaptationWorkaroundBuffer =
|
codecNeedsAdaptationWorkaroundBuffer =
|
||||||
codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_ALWAYS
|
codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_ALWAYS
|
||||||
|| (codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION
|
|| (codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION
|
||||||
&& newFormat.width == codecInputFormat.width
|
&& newFormat.width == oldFormat.width
|
||||||
&& newFormat.height == codecInputFormat.height);
|
&& newFormat.height == oldFormat.height);
|
||||||
codecInputFormat = newFormat;
|
codecInputFormat = newFormat;
|
||||||
if (drainAndUpdateCodecDrmSession) {
|
if (drainAndUpdateCodecDrmSession && !drainAndUpdateCodecDrmSessionV23()) {
|
||||||
drainAndUpdateCodecDrmSessionV23();
|
overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION:
|
case REUSE_RESULT_YES_WITHOUT_RECONFIGURATION:
|
||||||
if (updateOperatingRateOrReinitializeCodec(newFormat)) {
|
if (!updateCodecOperatingRate(newFormat)) {
|
||||||
// Codec re-initialization triggered.
|
overridingDiscardReasons |= DISCARD_REASON_OPERATING_RATE_CHANGED;
|
||||||
} else {
|
} else {
|
||||||
codecInputFormat = newFormat;
|
codecInputFormat = newFormat;
|
||||||
if (drainAndUpdateCodecDrmSession) {
|
if (drainAndUpdateCodecDrmSession && !drainAndUpdateCodecDrmSessionV23()) {
|
||||||
drainAndUpdateCodecDrmSessionV23();
|
overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException(); // Never happens.
|
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.
|
* 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 codecInfo A {@link MediaCodecInfo} describing the decoder.
|
||||||
* @param oldFormat The {@link Format} for which the existing instance is configured.
|
* @param oldFormat The {@link Format} for which the existing instance is configured.
|
||||||
* @param newFormat The new {@link Format}.
|
* @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 DecoderReuseEvaluation canReuseCodec(
|
||||||
protected int canKeepCodec(
|
MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
|
||||||
MediaCodecAdapter codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
|
return new DecoderReuseEvaluation(
|
||||||
return KEEP_CODEC_RESULT_NO;
|
codecInfo.name,
|
||||||
|
oldFormat,
|
||||||
|
newFormat,
|
||||||
|
REUSE_RESULT_NO,
|
||||||
|
DISCARD_REASON_REUSE_NOT_IMPLEMENTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1608,25 +1640,23 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
*
|
*
|
||||||
* @param format The {@link Format} for which the operating rate should be configured.
|
* @param format The {@link Format} for which the operating rate should be configured.
|
||||||
* @throws ExoPlaybackException If an error occurs releasing or initializing a codec.
|
* @throws ExoPlaybackException If an error occurs releasing or initializing a codec.
|
||||||
* @return Whether codec release and re-initialization was triggered, rather than the existing
|
* @return False if codec release and re-initialization was triggered. True in all other cases.
|
||||||
* codec being updated.
|
|
||||||
*/
|
*/
|
||||||
private boolean updateOperatingRateOrReinitializeCodec(Format format)
|
private boolean updateCodecOperatingRate(Format format) throws ExoPlaybackException {
|
||||||
throws ExoPlaybackException {
|
|
||||||
if (Util.SDK_INT < 23) {
|
if (Util.SDK_INT < 23) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
float newCodecOperatingRate =
|
float newCodecOperatingRate =
|
||||||
getCodecOperatingRateV23(playbackSpeed, format, getStreamFormats());
|
getCodecOperatingRateV23(playbackSpeed, format, getStreamFormats());
|
||||||
if (codecOperatingRate == newCodecOperatingRate) {
|
if (codecOperatingRate == newCodecOperatingRate) {
|
||||||
// No change.
|
// No change.
|
||||||
return false;
|
return true;
|
||||||
} else if (newCodecOperatingRate == CODEC_OPERATING_RATE_UNSET) {
|
} else if (newCodecOperatingRate == CODEC_OPERATING_RATE_UNSET) {
|
||||||
// The only way to clear the operating rate is to instantiate a new codec instance. See
|
// The only way to clear the operating rate is to instantiate a new codec instance. See
|
||||||
// [Internal ref: b/111543954].
|
// [Internal ref: b/111543954].
|
||||||
drainAndReinitializeCodec();
|
drainAndReinitializeCodec();
|
||||||
return true;
|
return false;
|
||||||
} else if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET
|
} else if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET
|
||||||
|| newCodecOperatingRate > assumedMinimumCodecOperatingRate) {
|
|| newCodecOperatingRate > assumedMinimumCodecOperatingRate) {
|
||||||
// We need to set the operating rate, either because we've set it previously or because it's
|
// 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);
|
codecParameters.putFloat(MediaFormat.KEY_OPERATING_RATE, newCodecOperatingRate);
|
||||||
codec.setParameters(codecParameters);
|
codec.setParameters(codecParameters);
|
||||||
codecOperatingRate = newCodecOperatingRate;
|
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) {
|
if (codecReceivedBuffers) {
|
||||||
codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM;
|
codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM;
|
||||||
if (codecNeedsFlushWorkaround || codecNeedsEosFlushWorkaround) {
|
if (codecNeedsFlushWorkaround || codecNeedsEosFlushWorkaround) {
|
||||||
codecDrainAction = DRAIN_ACTION_REINITIALIZE;
|
codecDrainAction = DRAIN_ACTION_REINITIALIZE;
|
||||||
|
return false;
|
||||||
} else {
|
} else {
|
||||||
codecDrainAction = DRAIN_ACTION_FLUSH;
|
codecDrainAction = DRAIN_ACTION_FLUSH;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts draining the codec to update its DRM session. The update may occur immediately if no
|
* Starts draining the codec to flush it and update its DRM session, or to release and
|
||||||
* buffers have been queued to the codec.
|
* 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.
|
* @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.
|
@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) {
|
if (codecReceivedBuffers) {
|
||||||
codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM;
|
codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM;
|
||||||
if (codecNeedsFlushWorkaround || codecNeedsEosFlushWorkaround) {
|
if (codecNeedsFlushWorkaround || codecNeedsEosFlushWorkaround) {
|
||||||
codecDrainAction = DRAIN_ACTION_REINITIALIZE;
|
codecDrainAction = DRAIN_ACTION_REINITIALIZE;
|
||||||
|
return false;
|
||||||
} else {
|
} else {
|
||||||
codecDrainAction = DRAIN_ACTION_FLUSH_AND_UPDATE_DRM_SESSION;
|
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.
|
// Nothing has been queued to the decoder, so we can do the update immediately.
|
||||||
updateDrmSessionV23();
|
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.analytics.AnalyticsListener;
|
||||||
import com.google.android.exoplayer2.audio.AudioAttributes;
|
import com.google.android.exoplayer2.audio.AudioAttributes;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
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.metadata.Metadata;
|
||||||
import com.google.android.exoplayer2.source.LoadEventInfo;
|
import com.google.android.exoplayer2.source.LoadEventInfo;
|
||||||
import com.google.android.exoplayer2.source.MediaLoadData;
|
import com.google.android.exoplayer2.source.MediaLoadData;
|
||||||
@ -328,7 +329,8 @@ public class EventLogger implements AnalyticsListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAudioInputFormatChanged(EventTime eventTime, Format format) {
|
public void onAudioInputFormatChanged(
|
||||||
|
EventTime eventTime, Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||||
logd(eventTime, "audioInputFormat", Format.toLogString(format));
|
logd(eventTime, "audioInputFormat", Format.toLogString(format));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,7 +395,8 @@ public class EventLogger implements AnalyticsListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onVideoInputFormatChanged(EventTime eventTime, Format format) {
|
public void onVideoInputFormatChanged(
|
||||||
|
EventTime eventTime, Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
|
||||||
logd(eventTime, "videoInputFormat", Format.toLogString(format));
|
logd(eventTime, "videoInputFormat", Format.toLogString(format));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.video;
|
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 static java.lang.Math.max;
|
||||||
|
|
||||||
import android.os.Handler;
|
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.DecoderCounters;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderException;
|
import com.google.android.exoplayer2.decoder.DecoderException;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
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;
|
||||||
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
|
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
|
||||||
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||||
@ -97,9 +101,12 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
|||||||
|
|
||||||
private Format inputFormat;
|
private Format inputFormat;
|
||||||
private Format outputFormat;
|
private Format outputFormat;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private Decoder<
|
private Decoder<
|
||||||
VideoDecoderInputBuffer, ? extends VideoDecoderOutputBuffer, ? extends DecoderException>
|
VideoDecoderInputBuffer, ? extends VideoDecoderOutputBuffer, ? extends DecoderException>
|
||||||
decoder;
|
decoder;
|
||||||
|
|
||||||
private VideoDecoderInputBuffer inputBuffer;
|
private VideoDecoderInputBuffer inputBuffer;
|
||||||
private VideoDecoderOutputBuffer outputBuffer;
|
private VideoDecoderOutputBuffer outputBuffer;
|
||||||
@Nullable private Surface surface;
|
@Nullable private Surface surface;
|
||||||
@ -366,7 +373,24 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
|||||||
|
|
||||||
if (decoder == null) {
|
if (decoder == null) {
|
||||||
maybeInitDecoder();
|
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) {
|
if (decoderReceivedBuffers) {
|
||||||
// Signal end of stream and wait for any final output buffers before re-initialization.
|
// Signal end of stream and wait for any final output buffers before re-initialization.
|
||||||
decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM;
|
decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM;
|
||||||
@ -376,8 +400,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
|||||||
maybeInitDecoder();
|
maybeInitDecoder();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
eventDispatcher.inputFormatChanged(inputFormat, evaluation);
|
||||||
eventDispatcher.inputFormatChanged(inputFormat);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -626,14 +649,18 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
|||||||
protected abstract void setDecoderOutputMode(@C.VideoOutputMode int outputMode);
|
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 oldFormat The previous format.
|
||||||
* @param newFormat The new 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) {
|
protected DecoderReuseEvaluation canReuseDecoder(
|
||||||
return false;
|
String decoderName, Format oldFormat, Format newFormat) {
|
||||||
|
return new DecoderReuseEvaluation(
|
||||||
|
decoderName, oldFormat, newFormat, REUSE_RESULT_NO, DISCARD_REASON_REUSE_NOT_IMPLEMENTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
@ -15,7 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.video;
|
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.max;
|
||||||
import static java.lang.Math.min;
|
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.RendererCapabilities;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
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.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecDecoderException;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecDecoderException;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
|
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.MediaCodecRenderer;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
|
||||||
@ -566,15 +569,24 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@KeepCodecResult
|
protected DecoderReuseEvaluation canReuseCodec(
|
||||||
protected int canKeepCodec(
|
MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
|
||||||
MediaCodecAdapter codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
|
DecoderReuseEvaluation evaluation = codecInfo.canReuseCodec(oldFormat, newFormat);
|
||||||
if (newFormat.width > codecMaxValues.width
|
|
||||||
|| newFormat.height > codecMaxValues.height
|
@DecoderDiscardReasons int discardReasons = evaluation.discardReasons;
|
||||||
|| getMaxInputSize(codecInfo, newFormat) > codecMaxValues.inputSize) {
|
if (newFormat.width > codecMaxValues.width || newFormat.height > codecMaxValues.height) {
|
||||||
return KEEP_CODEC_RESULT_NO;
|
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
|
@CallSuper
|
||||||
@ -621,9 +633,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
|
@Nullable
|
||||||
super.onInputFormatChanged(formatHolder);
|
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
|
||||||
eventDispatcher.inputFormatChanged(formatHolder.format);
|
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.
|
// from format to avoid codec re-use being ruled out for only this reason.
|
||||||
streamFormat = streamFormat.buildUpon().setColorInfo(format.colorInfo).build();
|
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 |=
|
haveUnknownDimensions |=
|
||||||
(streamFormat.width == Format.NO_VALUE || streamFormat.height == Format.NO_VALUE);
|
(streamFormat.width == Format.NO_VALUE || streamFormat.height == Format.NO_VALUE);
|
||||||
maxWidth = max(maxWidth, streamFormat.width);
|
maxWidth = max(maxWidth, streamFormat.width);
|
||||||
|
@ -25,6 +25,7 @@ import androidx.annotation.Nullable;
|
|||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.Renderer;
|
import com.google.android.exoplayer2.Renderer;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
|
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,12 +53,23 @@ public interface VideoRendererEventListener {
|
|||||||
default void onVideoDecoderInitialized(
|
default void onVideoDecoderInitialized(
|
||||||
String decoderName, long initializedTimestampMs, long initializationDurationMs) {}
|
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.
|
* Called when the format of the media being consumed by the renderer changes.
|
||||||
*
|
*
|
||||||
* @param format The new format.
|
* @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
|
* 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) {
|
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;
|
package com.google.android.exoplayer2.mediacodec;
|
||||||
|
|
||||||
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_NO;
|
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED;
|
||||||
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_YES_WITH_FLUSH;
|
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_INITIALIZATION_DATA_CHANGED;
|
||||||
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION;
|
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.AUDIO_AAC;
|
||||||
import static com.google.android.exoplayer2.util.MimeTypes.VIDEO_AV1;
|
import static com.google.android.exoplayer2.util.MimeTypes.VIDEO_AV1;
|
||||||
import static com.google.android.exoplayer2.util.MimeTypes.VIDEO_H264;
|
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 androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
|
||||||
import com.google.android.exoplayer2.video.ColorInfo;
|
import com.google.android.exoplayer2.video.ColorInfo;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -71,7 +78,14 @@ public final class MediaCodecInfoTest {
|
|||||||
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true);
|
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true);
|
||||||
|
|
||||||
Format hdAv1Format = FORMAT_H264_HD.buildUpon().setSampleMimeType(VIDEO_AV1).build();
|
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
|
@Test
|
||||||
@ -79,24 +93,42 @@ public final class MediaCodecInfoTest {
|
|||||||
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true);
|
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true);
|
||||||
|
|
||||||
Format hdRotatedFormat = FORMAT_H264_HD.buildUpon().setRotationDegrees(90).build();
|
Format hdRotatedFormat = FORMAT_H264_HD.buildUpon().setRotationDegrees(90).build();
|
||||||
assertThat(codecInfo.canKeepCodec(FORMAT_H264_HD, hdRotatedFormat))
|
assertThat(codecInfo.canReuseCodec(FORMAT_H264_HD, hdRotatedFormat))
|
||||||
.isEqualTo(KEEP_CODEC_RESULT_NO);
|
.isEqualTo(
|
||||||
|
new DecoderReuseEvaluation(
|
||||||
|
codecInfo.name,
|
||||||
|
FORMAT_H264_HD,
|
||||||
|
hdRotatedFormat,
|
||||||
|
REUSE_RESULT_NO,
|
||||||
|
DISCARD_REASON_VIDEO_ROTATION_CHANGED));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void canKeepCodec_withResolutionChange_adaptiveCodec_returnsYesWithReconfiguration() {
|
public void canKeepCodec_withResolutionChange_adaptiveCodec_returnsYesWithReconfiguration() {
|
||||||
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true);
|
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true);
|
||||||
|
|
||||||
assertThat(codecInfo.canKeepCodec(FORMAT_H264_HD, FORMAT_H264_4K))
|
assertThat(codecInfo.canReuseCodec(FORMAT_H264_HD, FORMAT_H264_4K))
|
||||||
.isEqualTo(KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION);
|
.isEqualTo(
|
||||||
|
new DecoderReuseEvaluation(
|
||||||
|
codecInfo.name,
|
||||||
|
FORMAT_H264_HD,
|
||||||
|
FORMAT_H264_4K,
|
||||||
|
REUSE_RESULT_YES_WITH_RECONFIGURATION,
|
||||||
|
/* discardReasons= */ 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void canKeepCodec_withResolutionChange_nonAdaptiveCodec_returnsNo() {
|
public void canKeepCodec_withResolutionChange_nonAdaptiveCodec_returnsNo() {
|
||||||
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ false);
|
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ false);
|
||||||
|
|
||||||
assertThat(codecInfo.canKeepCodec(FORMAT_H264_HD, FORMAT_H264_4K))
|
assertThat(codecInfo.canReuseCodec(FORMAT_H264_HD, FORMAT_H264_4K))
|
||||||
.isEqualTo(KEEP_CODEC_RESULT_NO);
|
.isEqualTo(
|
||||||
|
new DecoderReuseEvaluation(
|
||||||
|
codecInfo.name,
|
||||||
|
FORMAT_H264_HD,
|
||||||
|
FORMAT_H264_4K,
|
||||||
|
REUSE_RESULT_NO,
|
||||||
|
DISCARD_REASON_VIDEO_RESOLUTION_CHANGED));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -105,8 +137,14 @@ public final class MediaCodecInfoTest {
|
|||||||
|
|
||||||
Format hdVariantFormat =
|
Format hdVariantFormat =
|
||||||
FORMAT_H264_HD.buildUpon().setInitializationData(ImmutableList.of(new byte[] {0})).build();
|
FORMAT_H264_HD.buildUpon().setInitializationData(ImmutableList.of(new byte[] {0})).build();
|
||||||
assertThat(codecInfo.canKeepCodec(FORMAT_H264_HD, hdVariantFormat))
|
assertThat(codecInfo.canReuseCodec(FORMAT_H264_HD, hdVariantFormat))
|
||||||
.isEqualTo(KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION);
|
.isEqualTo(
|
||||||
|
new DecoderReuseEvaluation(
|
||||||
|
codecInfo.name,
|
||||||
|
FORMAT_H264_HD,
|
||||||
|
hdVariantFormat,
|
||||||
|
REUSE_RESULT_YES_WITH_RECONFIGURATION,
|
||||||
|
/* discardReasons= */ 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -115,8 +153,14 @@ public final class MediaCodecInfoTest {
|
|||||||
|
|
||||||
Format hdrVariantFormat =
|
Format hdrVariantFormat =
|
||||||
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT601)).build();
|
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT601)).build();
|
||||||
assertThat(codecInfo.canKeepCodec(hdrVariantFormat, FORMAT_H264_4K))
|
assertThat(codecInfo.canReuseCodec(hdrVariantFormat, FORMAT_H264_4K))
|
||||||
.isEqualTo(KEEP_CODEC_RESULT_NO);
|
.isEqualTo(
|
||||||
|
new DecoderReuseEvaluation(
|
||||||
|
codecInfo.name,
|
||||||
|
hdrVariantFormat,
|
||||||
|
FORMAT_H264_4K,
|
||||||
|
REUSE_RESULT_NO,
|
||||||
|
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -125,8 +169,14 @@ public final class MediaCodecInfoTest {
|
|||||||
|
|
||||||
Format hdrVariantFormat =
|
Format hdrVariantFormat =
|
||||||
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT601)).build();
|
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT601)).build();
|
||||||
assertThat(codecInfo.canKeepCodec(FORMAT_H264_4K, hdrVariantFormat))
|
assertThat(codecInfo.canReuseCodec(FORMAT_H264_4K, hdrVariantFormat))
|
||||||
.isEqualTo(KEEP_CODEC_RESULT_NO);
|
.isEqualTo(
|
||||||
|
new DecoderReuseEvaluation(
|
||||||
|
codecInfo.name,
|
||||||
|
FORMAT_H264_4K,
|
||||||
|
hdrVariantFormat,
|
||||||
|
REUSE_RESULT_NO,
|
||||||
|
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -137,18 +187,28 @@ public final class MediaCodecInfoTest {
|
|||||||
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT601)).build();
|
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT601)).build();
|
||||||
Format hdrVariantFormat2 =
|
Format hdrVariantFormat2 =
|
||||||
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT709)).build();
|
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT709)).build();
|
||||||
assertThat(codecInfo.canKeepCodec(hdrVariantFormat1, hdrVariantFormat2))
|
assertThat(codecInfo.canReuseCodec(hdrVariantFormat1, hdrVariantFormat2))
|
||||||
.isEqualTo(KEEP_CODEC_RESULT_NO);
|
.isEqualTo(
|
||||||
assertThat(codecInfo.canKeepCodec(hdrVariantFormat1, hdrVariantFormat2))
|
new DecoderReuseEvaluation(
|
||||||
.isEqualTo(KEEP_CODEC_RESULT_NO);
|
codecInfo.name,
|
||||||
|
hdrVariantFormat1,
|
||||||
|
hdrVariantFormat2,
|
||||||
|
REUSE_RESULT_NO,
|
||||||
|
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void canKeepCodec_audioWithDifferentChannelCounts_returnsNo() {
|
public void canKeepCodec_audioWithDifferentChannelCounts_returnsNo() {
|
||||||
MediaCodecInfo codecInfo = buildAacCodecInfo();
|
MediaCodecInfo codecInfo = buildAacCodecInfo();
|
||||||
|
|
||||||
assertThat(codecInfo.canKeepCodec(FORMAT_AAC_STEREO, FORMAT_AAC_SURROUND))
|
assertThat(codecInfo.canReuseCodec(FORMAT_AAC_STEREO, FORMAT_AAC_SURROUND))
|
||||||
.isEqualTo(KEEP_CODEC_RESULT_NO);
|
.isEqualTo(
|
||||||
|
new DecoderReuseEvaluation(
|
||||||
|
codecInfo.name,
|
||||||
|
FORMAT_AAC_STEREO,
|
||||||
|
FORMAT_AAC_SURROUND,
|
||||||
|
REUSE_RESULT_NO,
|
||||||
|
DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -156,8 +216,14 @@ public final class MediaCodecInfoTest {
|
|||||||
MediaCodecInfo codecInfo = buildAacCodecInfo();
|
MediaCodecInfo codecInfo = buildAacCodecInfo();
|
||||||
|
|
||||||
Format stereoVariantFormat = FORMAT_AAC_STEREO.buildUpon().setAverageBitrate(100).build();
|
Format stereoVariantFormat = FORMAT_AAC_STEREO.buildUpon().setAverageBitrate(100).build();
|
||||||
assertThat(codecInfo.canKeepCodec(FORMAT_AAC_STEREO, stereoVariantFormat))
|
assertThat(codecInfo.canReuseCodec(FORMAT_AAC_STEREO, stereoVariantFormat))
|
||||||
.isEqualTo(KEEP_CODEC_RESULT_YES_WITH_FLUSH);
|
.isEqualTo(
|
||||||
|
new DecoderReuseEvaluation(
|
||||||
|
codecInfo.name,
|
||||||
|
FORMAT_AAC_STEREO,
|
||||||
|
stereoVariantFormat,
|
||||||
|
REUSE_RESULT_YES_WITH_FLUSH,
|
||||||
|
/* discardReasons= */ 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -169,8 +235,14 @@ public final class MediaCodecInfoTest {
|
|||||||
.buildUpon()
|
.buildUpon()
|
||||||
.setInitializationData(ImmutableList.of(new byte[] {0}))
|
.setInitializationData(ImmutableList.of(new byte[] {0}))
|
||||||
.build();
|
.build();
|
||||||
assertThat(codecInfo.canKeepCodec(FORMAT_AAC_STEREO, stereoVariantFormat))
|
assertThat(codecInfo.canReuseCodec(FORMAT_AAC_STEREO, stereoVariantFormat))
|
||||||
.isEqualTo(KEEP_CODEC_RESULT_NO);
|
.isEqualTo(
|
||||||
|
new DecoderReuseEvaluation(
|
||||||
|
codecInfo.name,
|
||||||
|
FORMAT_AAC_STEREO,
|
||||||
|
stereoVariantFormat,
|
||||||
|
REUSE_RESULT_NO,
|
||||||
|
DISCARD_REASON_INITIALIZATION_DATA_CHANGED));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -30,6 +30,7 @@ import com.google.android.exoplayer2.Format;
|
|||||||
import com.google.android.exoplayer2.FormatHolder;
|
import com.google.android.exoplayer2.FormatHolder;
|
||||||
import com.google.android.exoplayer2.Renderer;
|
import com.google.android.exoplayer2.Renderer;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
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.MediaCodecAdapter;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
|
||||||
@ -165,12 +166,15 @@ import java.util.ArrayList;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
|
@Nullable
|
||||||
super.onInputFormatChanged(formatHolder);
|
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
|
// 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.
|
// queue of expected output timestamps before those of buffers that have already been queued.
|
||||||
minimumInsertIndex = startIndex + queueSize;
|
minimumInsertIndex = startIndex + queueSize;
|
||||||
inputFormatChanged = true;
|
inputFormatChanged = true;
|
||||||
|
return evaluation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -55,7 +55,7 @@ public class FakeAudioRenderer extends FakeRenderer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onFormatChanged(Format format) {
|
protected void onFormatChanged(Format format) {
|
||||||
eventDispatcher.inputFormatChanged(format);
|
eventDispatcher.inputFormatChanged(format, /* decoderReuseEvaluation= */ null);
|
||||||
eventDispatcher.decoderInitialized(
|
eventDispatcher.decoderInitialized(
|
||||||
/* decoderName= */ "fake.audio.decoder",
|
/* decoderName= */ "fake.audio.decoder",
|
||||||
/* initializedTimestampMs= */ SystemClock.elapsedRealtime(),
|
/* initializedTimestampMs= */ SystemClock.elapsedRealtime(),
|
||||||
|
@ -85,7 +85,7 @@ public class FakeVideoRenderer extends FakeRenderer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onFormatChanged(Format format) {
|
protected void onFormatChanged(Format format) {
|
||||||
eventDispatcher.inputFormatChanged(format);
|
eventDispatcher.inputFormatChanged(format, /* decoderReuseEvaluation= */ null);
|
||||||
eventDispatcher.decoderInitialized(
|
eventDispatcher.decoderInitialized(
|
||||||
/* decoderName= */ "fake.video.decoder",
|
/* decoderName= */ "fake.video.decoder",
|
||||||
/* initializedTimestampMs= */ SystemClock.elapsedRealtime(),
|
/* initializedTimestampMs= */ SystemClock.elapsedRealtime(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user