diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1007a97612..6de9349f32 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -70,6 +70,9 @@ ([#3497](https://github.com/google/ExoPlayer/issues/3497)). * Add `PlayerView.isControllerVisible` ([#4385](https://github.com/google/ExoPlayer/issues/4385)). +* Improved performance when playing high frame-rate content, and when playing + at greater than 1x speed + ([#2777](https://github.com/google/ExoPlayer/issues/2777)). * Expose all internal ID3 data stored in MP4 udta boxes, and switch from using CommentFrame to InternalFrame for frames with gapless metadata in MP4. * Allow setting the `Looper`, which is used to access the player, in diff --git a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java index d3a5726585..cb917b9b79 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java @@ -136,11 +136,6 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { onPositionReset(positionUs, false); } - @Override - public final void setOperatingRate(float operatingRate) { - onOperatingRateChanged(operatingRate); - } - @Override public final void stop() throws ExoPlaybackException { Assertions.checkState(state == STATE_STARTED); @@ -221,17 +216,6 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { // Do nothing. } - /** - * Called when the operating rate is changed. - *
- * The default implementation is a no-op. - * - * @param operatingRate The new operating rate. - */ - protected void onOperatingRateChanged(float operatingRate) { - // Do nothing. - } - /** * Called when the renderer is started. *
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 61e9524fd0..22205e77f4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -78,6 +78,7 @@ import java.util.Collections; private static final int MSG_SET_SHUFFLE_ENABLED = 13; private static final int MSG_SEND_MESSAGE = 14; private static final int MSG_SEND_MESSAGE_TO_TARGET_THREAD = 15; + private static final int MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL = 16; private static final int PREPARING_SOURCE_INTERVAL_MS = 10; private static final int RENDERING_INTERVAL_MS = 10; @@ -275,9 +276,9 @@ import java.util.Collections; @Override public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { - eventHandler.obtainMessage(MSG_PLAYBACK_PARAMETERS_CHANGED, playbackParameters).sendToTarget(); - updateTrackSelectionPlaybackSpeed(playbackParameters.speed); - updateRendererOperatingRate(playbackParameters.speed); + handler + .obtainMessage(MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL, playbackParameters) + .sendToTarget(); } // Handler.Callback implementation. @@ -329,6 +330,9 @@ import java.util.Collections; case MSG_TRACK_SELECTION_INVALIDATED: reselectTracksInternal(); break; + case MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL: + handlePlaybackParameters((PlaybackParameters) msg.obj); + break; case MSG_SEND_MESSAGE: sendMessageInternal((PlayerMessage) msg.obj); break; @@ -1100,14 +1104,6 @@ import java.util.Collections; } } - private void updateRendererOperatingRate(float operatingRate) { - for (Renderer renderer : renderers) { - if (renderer != null) { - renderer.setOperatingRate(operatingRate); - } - } - } - private boolean shouldTransitionToReadyState(boolean renderersReadyOrEnded) { if (enabledRenderers.length == 0) { // If there are no enabled renderers, determine whether we're ready based on the timeline. @@ -1557,6 +1553,17 @@ import java.util.Collections; maybeContinueLoading(); } + private void handlePlaybackParameters(PlaybackParameters playbackParameters) + throws ExoPlaybackException { + eventHandler.obtainMessage(MSG_PLAYBACK_PARAMETERS_CHANGED, playbackParameters).sendToTarget(); + updateTrackSelectionPlaybackSpeed(playbackParameters.speed); + for (Renderer renderer : renderers) { + if (renderer != null) { + renderer.setOperatingRate(playbackParameters.speed); + } + } + } + private void maybeContinueLoading() { MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod(); long nextLoadPositionUs = loadingPeriodHolder.getNextLoadPositionUs(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java index f873eb5c7c..c29017856f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java @@ -193,11 +193,16 @@ public interface Renderer extends PlayerMessage.Target { void resetPosition(long positionUs) throws ExoPlaybackException; /** - * Sets the operating rate of this renderer. + * Sets the operating rate of this renderer, where 1 is the default rate, 2 is twice the default + * rate, 0.5 is half the default rate and so on. The operating rate is a hint to the renderer of + * the speed at which playback will proceed, and may be used for resource planning. * - * @param operatingRate The renderer operating rate. + *
The default implementation is a no-op.
+ *
+ * @param operatingRate The operating rate.
+ * @throws ExoPlaybackException If an error occurs handling the operating rate.
*/
- void setOperatingRate(float operatingRate);
+ default void setOperatingRate(float operatingRate) throws ExoPlaybackException {};
/**
* Incrementally renders the {@link SampleStream}.
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java
index 497048114d..689cfc41c6 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java
@@ -231,7 +231,12 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener,
AudioSink audioSink) {
- super(C.TRACK_TYPE_AUDIO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys);
+ super(
+ C.TRACK_TYPE_AUDIO,
+ mediaCodecSelector,
+ drmSessionManager,
+ playClearSamplesWithoutKeys,
+ /* assumedMinimumCodecOperatingRate= */ 44100);
this.context = context.getApplicationContext();
this.audioSink = audioSink;
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
@@ -316,13 +321,18 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
@Override
- protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format,
- MediaCrypto crypto, float codecOperatingRate) {
+ protected void configureCodec(
+ MediaCodecInfo codecInfo,
+ MediaCodec codec,
+ Format format,
+ MediaCrypto crypto,
+ float codecOperatingRate) {
codecMaxInputSize = getCodecMaxInputSize(codecInfo, format, getStreamFormats());
codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name);
passthroughEnabled = codecInfo.passthrough;
String codecMimeType = codecInfo.mimeType == null ? MimeTypes.AUDIO_RAW : codecInfo.mimeType;
- MediaFormat mediaFormat = getMediaFormat(format, codecMimeType, codecMaxInputSize, codecOperatingRate);
+ MediaFormat mediaFormat =
+ getMediaFormat(format, codecMimeType, codecMaxInputSize, codecOperatingRate);
codec.configure(mediaFormat, /* surface= */ null, crypto, /* flags= */ 0);
if (passthroughEnabled) {
// Store the input MIME type if we're using the passthrough codec.
@@ -350,6 +360,14 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
return this;
}
+ @Override
+ protected float getCodecOperatingRate(
+ float operatingRate, Format format, Format[] streamFormats) {
+ return format.sampleRate == Format.NO_VALUE
+ ? CODEC_OPERATING_RATE_UNSET
+ : (format.sampleRate * operatingRate);
+ }
+
@Override
protected void onCodecInitialized(String name, long initializedTimestampMs,
long initializationDurationMs) {
@@ -633,12 +651,13 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
* @param format The format of the media.
* @param codecMimeType The MIME type handled by the codec.
* @param codecMaxInputSize The maximum input size supported by the codec.
- * @param codecOperatingRate
+ * @param codecOperatingRate The codec operating rate, or {@link #CODEC_OPERATING_RATE_UNSET} if
+ * no codec operating rate should be set.
* @return The framework media format.
*/
@SuppressLint("InlinedApi")
- protected MediaFormat getMediaFormat(Format format, String codecMimeType, int codecMaxInputSize,
- float codecOperatingRate) {
+ protected MediaFormat getMediaFormat(
+ Format format, String codecMimeType, int codecMaxInputSize, float codecOperatingRate) {
MediaFormat mediaFormat = new MediaFormat();
// Set format parameters that should always be set.
mediaFormat.setString(MediaFormat.KEY_MIME, codecMimeType);
@@ -650,9 +669,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
// Set codec configuration values.
if (Util.SDK_INT >= 23) {
mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, 0 /* realtime priority */);
- if (format.sampleRate != Format.NO_VALUE) {
- mediaFormat.setFloat(
- MediaFormat.KEY_OPERATING_RATE, codecOperatingRate * format.sampleRate);
+ if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET) {
+ mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate);
}
}
return mediaFormat;
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
index c4c29a451c..4982f86274 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
@@ -21,6 +21,7 @@ import android.media.MediaCodec.CodecException;
import android.media.MediaCodec.CryptoException;
import android.media.MediaCrypto;
import android.media.MediaFormat;
+import android.os.Bundle;
import android.os.Looper;
import android.os.SystemClock;
import android.support.annotation.CheckResult;
@@ -161,6 +162,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
}
+ /** Indicates no codec operating rate should be set. */
+ protected static final float CODEC_OPERATING_RATE_UNSET = -1;
+
private static final String TAG = "MediaCodecRenderer";
/**
@@ -264,6 +268,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@Nullable
private final DrmSessionManager
- * The default implementation is a no-op.
- */
- protected void updateCodecOperatingRate(MediaCodec codec, Format format, float codecOperatingRate) {
- // Do nothing.
- }
-
/**
* Called when the output format of the {@link MediaCodec} changes.
*
@@ -1130,6 +1138,77 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return 0;
}
+ /**
+ * Returns the {@link MediaFormat#KEY_OPERATING_RATE} value for a given renderer operating rate,
+ * current format and set of possible stream formats.
+ *
+ * The default implementation returns {@link #CODEC_OPERATING_RATE_UNSET}.
+ *
+ * @param operatingRate The renderer operating rate.
+ * @param format The format for which the codec is being configured.
+ * @param streamFormats The possible stream formats.
+ * @return The codec operating rate, or {@link #CODEC_OPERATING_RATE_UNSET} if no codec operating
+ * rate should be set.
+ */
+ protected float getCodecOperatingRate(
+ float operatingRate, Format format, Format[] streamFormats) {
+ return CODEC_OPERATING_RATE_UNSET;
+ }
+
+ /**
+ * Updates the codec operating rate, and the codec itself if necessary.
+ *
+ * @throws ExoPlaybackException If an error occurs releasing or initializing a codec.
+ */
+ private void updateCodecOperatingRate() throws ExoPlaybackException {
+ if (format == null || Util.SDK_INT < 23) {
+ return;
+ }
+
+ float codecOperatingRate =
+ getCodecOperatingRate(rendererOperatingRate, format, getStreamFormats());
+ if (this.codecOperatingRate == codecOperatingRate) {
+ return;
+ }
+
+ this.codecOperatingRate = codecOperatingRate;
+ if (codec == null || codecReinitializationState != REINITIALIZATION_STATE_NONE) {
+ // Either no codec, or it's about to be reinitialized anyway.
+ } else if (codecOperatingRate == CODEC_OPERATING_RATE_UNSET
+ && codecConfiguredWithOperatingRate) {
+ // We need to clear the operating rate. The only way to do so is to instantiate a new codec
+ // instance. See [Internal ref: b/71987865].
+ reinitializeCodec();
+ } else if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET
+ && (codecConfiguredWithOperatingRate
+ || codecOperatingRate > assumedMinimumCodecOperatingRate)) {
+ // We need to set the operating rate, either because we've set it previously or because it's
+ // above the assumed minimum rate.
+ Bundle codecParameters = new Bundle();
+ codecParameters.putFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate);
+ codec.setParameters(codecParameters);
+ codecConfiguredWithOperatingRate = true;
+ }
+ }
+
+ /**
+ * Starts the process of releasing the existing codec and initializing a new one. This may occur
+ * immediately, or be deferred until any final output buffers have been dequeued.
+ *
+ * @throws ExoPlaybackException If an error occurs releasing or initializing a codec.
+ */
+ private void reinitializeCodec() throws ExoPlaybackException {
+ availableCodecInfos = null;
+ if (codecReceivedBuffers) {
+ // Signal end of stream and wait for any final output buffers before re-initialization.
+ codecReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM;
+ } else {
+ // There aren't any final output buffers, so perform re-initialization immediately.
+ releaseCodec();
+ maybeInitCodec();
+ }
+ }
+
/**
* @return Whether it may be possible to drain more output data.
* @throws ExoPlaybackException If an error occurs draining the output buffer.
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
index f9a10fa275..42d9549075 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
@@ -23,7 +23,6 @@ import android.media.MediaCodec;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCrypto;
import android.media.MediaFormat;
-import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.support.annotation.CallSuper;
@@ -206,7 +205,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
@Nullable DrmSessionManager