From badd9356f8d617802724e58f7c7aaab40f026134 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Fri, 20 Jul 2018 16:15:30 +0100 Subject: [PATCH] Refine use of KEY_OPERATING_RATE --- RELEASENOTES.md | 3 + .../android/exoplayer2/BaseRenderer.java | 16 -- .../exoplayer2/ExoPlayerImplInternal.java | 29 ++-- .../google/android/exoplayer2/Renderer.java | 11 +- .../audio/MediaCodecAudioRenderer.java | 38 +++-- .../mediacodec/MediaCodecRenderer.java | 149 ++++++++++++++---- .../video/MediaCodecVideoRenderer.java | 58 ++++--- .../testutil/DebugRenderersFactory.java | 11 +- 8 files changed, 216 insertions(+), 99 deletions(-) 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 drmSessionManager; private final boolean playClearSamplesWithoutKeys; + private final float assumedMinimumCodecOperatingRate; private final DecoderInputBuffer buffer; private final DecoderInputBuffer flagsOnlyBuffer; private final FormatHolder formatHolder; @@ -274,7 +279,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private DrmSession drmSession; private DrmSession pendingDrmSession; private MediaCodec codec; - private float codecOperatingRate = 1.0f; + private float rendererOperatingRate; + private float codecOperatingRate; + private boolean codecConfiguredWithOperatingRate; private @Nullable ArrayDeque availableCodecInfos; private @Nullable DecoderInitializationException preferredDecoderInitializationException; private @Nullable MediaCodecInfo codecInfo; @@ -318,15 +325,22 @@ public abstract class MediaCodecRenderer extends BaseRenderer { * begin in parallel with key acquisition. This parameter specifies whether the renderer is * permitted to play clear regions of encrypted media files before {@code drmSessionManager} * has obtained the keys necessary to decrypt encrypted regions of the media. + * @param assumedMinimumCodecOperatingRate A codec operating rate that all codecs instantiated by + * this renderer are assumed to meet implicitly (i.e. without the operating rate being set + * explicitly using {@link MediaFormat#KEY_OPERATING_RATE}). */ - public MediaCodecRenderer(int trackType, MediaCodecSelector mediaCodecSelector, + public MediaCodecRenderer( + int trackType, + MediaCodecSelector mediaCodecSelector, @Nullable DrmSessionManager drmSessionManager, - boolean playClearSamplesWithoutKeys) { + boolean playClearSamplesWithoutKeys, + float assumedMinimumCodecOperatingRate) { super(trackType); Assertions.checkState(Util.SDK_INT >= 16); this.mediaCodecSelector = Assertions.checkNotNull(mediaCodecSelector); this.drmSessionManager = drmSessionManager; this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys; + this.assumedMinimumCodecOperatingRate = assumedMinimumCodecOperatingRate; buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance(); formatHolder = new FormatHolder(); @@ -334,6 +348,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { outputBufferInfo = new MediaCodec.BufferInfo(); codecReconfigurationState = RECONFIGURATION_STATE_NONE; codecReinitializationState = REINITIALIZATION_STATE_NONE; + codecOperatingRate = CODEC_OPERATING_RATE_UNSET; + rendererOperatingRate = 1f; } @Override @@ -386,13 +402,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer { * @param codec The {@link MediaCodec} to configure. * @param format The format for which the codec is being configured. * @param crypto For drm protected playbacks, a {@link MediaCrypto} to use for decryption. - * @param codecOperatingRate The {@link MediaFormat#KEY_OPERATING_RATE} to use for configuration. + * @param codecOperatingRate The codec operating rate, or {@link #CODEC_OPERATING_RATE_UNSET} if + * no codec operating rate should be set. * @throws DecoderQueryException If an error occurs querying {@code codecInfo}. */ - protected abstract void configureCodec(MediaCodecInfo codecInfo, - MediaCodec codec, Format format, - MediaCrypto crypto, - float codecOperatingRate) throws DecoderQueryException; + protected abstract void configureCodec( + MediaCodecInfo codecInfo, + MediaCodec codec, + Format format, + MediaCrypto crypto, + float codecOperatingRate) + throws DecoderQueryException; protected final void maybeInitCodec() throws ExoPlaybackException { if (codec != null || format == null) { @@ -484,11 +504,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } @Override - protected void onOperatingRateChanged(float operatingRate) { - codecOperatingRate = operatingRate; - if (format != null) { - updateCodecOperatingRate(codec, format, operatingRate); - } + public final void setOperatingRate(float operatingRate) throws ExoPlaybackException { + rendererOperatingRate = operatingRate; + updateCodecOperatingRate(); } @Override @@ -537,6 +555,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { codecReceivedEos = false; codecReconfigurationState = RECONFIGURATION_STATE_NONE; codecReinitializationState = REINITIALIZATION_STATE_NONE; + codecConfiguredWithOperatingRate = false; if (codec != null) { decoderCounters.decoderReleaseCount++; try { @@ -729,13 +748,21 @@ public abstract class MediaCodecRenderer extends BaseRenderer { long codecInitializedTimestamp; MediaCodec codec = null; String name = codecInfo.name; + updateCodecOperatingRate(); + boolean configureWithOperatingRate = codecOperatingRate > assumedMinimumCodecOperatingRate; try { codecInitializingTimestamp = SystemClock.elapsedRealtime(); TraceUtil.beginSection("createCodec:" + name); codec = MediaCodec.createByCodecName(name); TraceUtil.endSection(); TraceUtil.beginSection("configureCodec"); - configureCodec(codecInfo, codec, format, crypto, codecOperatingRate); + configureCodec( + codecInfo, + codec, + format, + crypto, + configureWithOperatingRate ? codecOperatingRate : CODEC_OPERATING_RATE_UNSET); + codecConfiguredWithOperatingRate = configureWithOperatingRate; TraceUtil.endSection(); TraceUtil.beginSection("startCodec"); codec.start(); @@ -1028,31 +1055,12 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } if (!keepingCodec) { - 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(); - } + reinitializeCodec(); } else { - if (Util.SDK_INT >= 23) { - updateCodecOperatingRate(codec, format, codecOperatingRate); - } + updateCodecOperatingRate(); } } - /** - * Updates the {@link MediaCodec} operating rate. - *

- * 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 drmSessionManager, boolean playClearSamplesWithoutKeys, @Nullable Handler eventHandler, @Nullable VideoRendererEventListener eventListener, int maxDroppedFramesToNotify) { - super(C.TRACK_TYPE_VIDEO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys); + super( + C.TRACK_TYPE_VIDEO, + mediaCodecSelector, + drmSessionManager, + playClearSamplesWithoutKeys, + /* assumedMinimumCodecOperatingRate= */ 30); this.allowedJoiningTimeMs = allowedJoiningTimeMs; this.maxDroppedFramesToNotify = maxDroppedFramesToNotify; this.context = context.getApplicationContext(); @@ -446,14 +450,21 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } @Override - protected void configureCodec(MediaCodecInfo codecInfo, - MediaCodec codec, - Format format, - MediaCrypto crypto, - float codecOperatingRate) throws DecoderQueryException { + protected void configureCodec( + MediaCodecInfo codecInfo, + MediaCodec codec, + Format format, + MediaCrypto crypto, + float codecOperatingRate) + throws DecoderQueryException { codecMaxValues = getCodecMaxValues(codecInfo, format, getStreamFormats()); - MediaFormat mediaFormat = getMediaFormat(format, codecMaxValues, deviceNeedsAutoFrcWorkaround, - tunnelingAudioSessionId, codecOperatingRate); + MediaFormat mediaFormat = + getMediaFormat( + format, + codecMaxValues, + codecOperatingRate, + deviceNeedsAutoFrcWorkaround, + tunnelingAudioSessionId); if (surface == null) { Assertions.checkState(shouldUseDummySurface(codecInfo)); if (dummySurface == null) { @@ -505,15 +516,19 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { buffersInCodecCount = 0; } - @TargetApi(23) @Override - protected void updateCodecOperatingRate(MediaCodec codec, Format format, float codecOperatingRate) { - if (format.frameRate == Format.NO_VALUE) { - return; + protected float getCodecOperatingRate( + float operatingRate, Format format, Format[] streamFormats) { + // Use the highest known stream frame-rate up front, to avoid having to reconfigure the codec + // should an adaptive switch to that stream occur. + float maxFrameRate = -1; + for (Format streamFormat : streamFormats) { + float streamFrameRate = streamFormat.frameRate; + if (streamFrameRate != Format.NO_VALUE) { + maxFrameRate = Math.max(maxFrameRate, streamFrameRate); + } } - Bundle codecParameters = new Bundle(); - codecParameters.putFloat(MediaFormat.KEY_OPERATING_RATE, format.frameRate * codecOperatingRate); - codec.setParameters(codecParameters); + return maxFrameRate == -1 ? CODEC_OPERATING_RATE_UNSET : (maxFrameRate * operatingRate); } @Override @@ -951,20 +966,21 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { * * @param format The format of media. * @param codecMaxValues Codec max values that should be used when configuring the decoder. + * @param codecOperatingRate The codec operating rate, or {@link #CODEC_OPERATING_RATE_UNSET} if + * no codec operating rate should be set. * @param deviceNeedsAutoFrcWorkaround Whether the device is known to enable frame-rate conversion * logic that negatively impacts ExoPlayer. * @param tunnelingAudioSessionId The audio session id to use for tunneling, or {@link * C#AUDIO_SESSION_ID_UNSET} if tunneling should not be enabled. - * @param codecOperatingRate * @return The framework {@link MediaFormat} that should be used to configure the decoder. */ @SuppressLint("InlinedApi") protected MediaFormat getMediaFormat( Format format, CodecMaxValues codecMaxValues, + float codecOperatingRate, boolean deviceNeedsAutoFrcWorkaround, - int tunnelingAudioSessionId, - float codecOperatingRate) { + int tunnelingAudioSessionId) { MediaFormat mediaFormat = new MediaFormat(); // Set format parameters that should always be set. mediaFormat.setString(MediaFormat.KEY_MIME, format.sampleMimeType); @@ -983,8 +999,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { // Set codec configuration values. if (Util.SDK_INT >= 23) { mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, 0 /* realtime priority */); - if (format.frameRate != Format.NO_VALUE) { - mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate * format.frameRate); + if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET) { + mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate); } } if (deviceNeedsAutoFrcWorkaround) { diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java index fa3b42fec7..02a8a0597d 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java @@ -81,15 +81,20 @@ public class DebugRenderersFactory extends DefaultRenderersFactory { } @Override - protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format, - MediaCrypto crypto, float codecOperatingRate) throws DecoderQueryException { + protected void configureCodec( + MediaCodecInfo codecInfo, + MediaCodec codec, + Format format, + MediaCrypto crypto, + float operatingRate) + throws DecoderQueryException { // If the codec is being initialized whilst the renderer is started, default behavior is to // render the first frame (i.e. the keyframe before the current position), then drop frames up // to the current playback position. For test runs that place a maximum limit on the number of // dropped frames allowed, this is not desired behavior. Hence we skip (rather than drop) // frames up to the current playback position [Internal: b/66494991]. skipToPositionBeforeRenderingFirstFrame = getState() == Renderer.STATE_STARTED; - super.configureCodec(codecInfo, codec, format, crypto, codecOperatingRate); + super.configureCodec(codecInfo, codec, format, crypto, operatingRate); } @Override