Commit badd9356 by Oliver Woodman

Refine use of KEY_OPERATING_RATE

parent f4219b55
...@@ -70,6 +70,9 @@ ...@@ -70,6 +70,9 @@
([#3497](https://github.com/google/ExoPlayer/issues/3497)). ([#3497](https://github.com/google/ExoPlayer/issues/3497)).
* Add `PlayerView.isControllerVisible` * Add `PlayerView.isControllerVisible`
([#4385](https://github.com/google/ExoPlayer/issues/4385)). ([#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 * Expose all internal ID3 data stored in MP4 udta boxes, and switch from using
CommentFrame to InternalFrame for frames with gapless metadata in MP4. CommentFrame to InternalFrame for frames with gapless metadata in MP4.
* Allow setting the `Looper`, which is used to access the player, in * Allow setting the `Looper`, which is used to access the player, in
......
...@@ -137,11 +137,6 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -137,11 +137,6 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
} }
@Override @Override
public final void setOperatingRate(float operatingRate) {
onOperatingRateChanged(operatingRate);
}
@Override
public final void stop() throws ExoPlaybackException { public final void stop() throws ExoPlaybackException {
Assertions.checkState(state == STATE_STARTED); Assertions.checkState(state == STATE_STARTED);
state = STATE_ENABLED; state = STATE_ENABLED;
...@@ -222,17 +217,6 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -222,17 +217,6 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
} }
/** /**
* Called when the operating rate is changed.
* <p>
* 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. * Called when the renderer is started.
* <p> * <p>
* The default implementation is a no-op. * The default implementation is a no-op.
......
...@@ -78,6 +78,7 @@ import java.util.Collections; ...@@ -78,6 +78,7 @@ import java.util.Collections;
private static final int MSG_SET_SHUFFLE_ENABLED = 13; private static final int MSG_SET_SHUFFLE_ENABLED = 13;
private static final int MSG_SEND_MESSAGE = 14; private static final int MSG_SEND_MESSAGE = 14;
private static final int MSG_SEND_MESSAGE_TO_TARGET_THREAD = 15; 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 PREPARING_SOURCE_INTERVAL_MS = 10;
private static final int RENDERING_INTERVAL_MS = 10; private static final int RENDERING_INTERVAL_MS = 10;
...@@ -275,9 +276,9 @@ import java.util.Collections; ...@@ -275,9 +276,9 @@ import java.util.Collections;
@Override @Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
eventHandler.obtainMessage(MSG_PLAYBACK_PARAMETERS_CHANGED, playbackParameters).sendToTarget(); handler
updateTrackSelectionPlaybackSpeed(playbackParameters.speed); .obtainMessage(MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL, playbackParameters)
updateRendererOperatingRate(playbackParameters.speed); .sendToTarget();
} }
// Handler.Callback implementation. // Handler.Callback implementation.
...@@ -329,6 +330,9 @@ import java.util.Collections; ...@@ -329,6 +330,9 @@ import java.util.Collections;
case MSG_TRACK_SELECTION_INVALIDATED: case MSG_TRACK_SELECTION_INVALIDATED:
reselectTracksInternal(); reselectTracksInternal();
break; break;
case MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL:
handlePlaybackParameters((PlaybackParameters) msg.obj);
break;
case MSG_SEND_MESSAGE: case MSG_SEND_MESSAGE:
sendMessageInternal((PlayerMessage) msg.obj); sendMessageInternal((PlayerMessage) msg.obj);
break; break;
...@@ -1100,14 +1104,6 @@ import java.util.Collections; ...@@ -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) { private boolean shouldTransitionToReadyState(boolean renderersReadyOrEnded) {
if (enabledRenderers.length == 0) { if (enabledRenderers.length == 0) {
// If there are no enabled renderers, determine whether we're ready based on the timeline. // If there are no enabled renderers, determine whether we're ready based on the timeline.
...@@ -1557,6 +1553,17 @@ import java.util.Collections; ...@@ -1557,6 +1553,17 @@ import java.util.Collections;
maybeContinueLoading(); 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() { private void maybeContinueLoading() {
MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod(); MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
long nextLoadPositionUs = loadingPeriodHolder.getNextLoadPositionUs(); long nextLoadPositionUs = loadingPeriodHolder.getNextLoadPositionUs();
......
...@@ -193,11 +193,16 @@ public interface Renderer extends PlayerMessage.Target { ...@@ -193,11 +193,16 @@ public interface Renderer extends PlayerMessage.Target {
void resetPosition(long positionUs) throws ExoPlaybackException; 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. * <p>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}. * Incrementally renders the {@link SampleStream}.
......
...@@ -231,7 +231,12 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -231,7 +231,12 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@Nullable Handler eventHandler, @Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener, @Nullable AudioRendererEventListener eventListener,
AudioSink audioSink) { 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.context = context.getApplicationContext();
this.audioSink = audioSink; this.audioSink = audioSink;
eventDispatcher = new EventDispatcher(eventHandler, eventListener); eventDispatcher = new EventDispatcher(eventHandler, eventListener);
...@@ -316,13 +321,18 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -316,13 +321,18 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
} }
@Override @Override
protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format, protected void configureCodec(
MediaCrypto crypto, float codecOperatingRate) { MediaCodecInfo codecInfo,
MediaCodec codec,
Format format,
MediaCrypto crypto,
float codecOperatingRate) {
codecMaxInputSize = getCodecMaxInputSize(codecInfo, format, getStreamFormats()); codecMaxInputSize = getCodecMaxInputSize(codecInfo, format, getStreamFormats());
codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name); codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name);
passthroughEnabled = codecInfo.passthrough; passthroughEnabled = codecInfo.passthrough;
String codecMimeType = codecInfo.mimeType == null ? MimeTypes.AUDIO_RAW : codecInfo.mimeType; 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); codec.configure(mediaFormat, /* surface= */ null, crypto, /* flags= */ 0);
if (passthroughEnabled) { if (passthroughEnabled) {
// Store the input MIME type if we're using the passthrough codec. // Store the input MIME type if we're using the passthrough codec.
...@@ -351,6 +361,14 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -351,6 +361,14 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
} }
@Override @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, protected void onCodecInitialized(String name, long initializedTimestampMs,
long initializationDurationMs) { long initializationDurationMs) {
eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs); eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs);
...@@ -633,12 +651,13 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -633,12 +651,13 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
* @param format The format of the media. * @param format The format of the media.
* @param codecMimeType The MIME type handled by the codec. * @param codecMimeType The MIME type handled by the codec.
* @param codecMaxInputSize The maximum input size supported 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. * @return The framework media format.
*/ */
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
protected MediaFormat getMediaFormat(Format format, String codecMimeType, int codecMaxInputSize, protected MediaFormat getMediaFormat(
float codecOperatingRate) { Format format, String codecMimeType, int codecMaxInputSize, float codecOperatingRate) {
MediaFormat mediaFormat = new MediaFormat(); MediaFormat mediaFormat = new MediaFormat();
// Set format parameters that should always be set. // Set format parameters that should always be set.
mediaFormat.setString(MediaFormat.KEY_MIME, codecMimeType); mediaFormat.setString(MediaFormat.KEY_MIME, codecMimeType);
...@@ -650,9 +669,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -650,9 +669,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
// Set codec configuration values. // Set codec configuration values.
if (Util.SDK_INT >= 23) { if (Util.SDK_INT >= 23) {
mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, 0 /* realtime priority */); mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, 0 /* realtime priority */);
if (format.sampleRate != Format.NO_VALUE) { if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET) {
mediaFormat.setFloat( mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate);
MediaFormat.KEY_OPERATING_RATE, codecOperatingRate * format.sampleRate);
} }
} }
return mediaFormat; return mediaFormat;
......
...@@ -23,7 +23,6 @@ import android.media.MediaCodec; ...@@ -23,7 +23,6 @@ import android.media.MediaCodec;
import android.media.MediaCodecInfo.CodecCapabilities; import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCrypto; import android.media.MediaCrypto;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.CallSuper; import android.support.annotation.CallSuper;
...@@ -206,7 +205,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -206,7 +205,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys, @Nullable Handler eventHandler, boolean playClearSamplesWithoutKeys, @Nullable Handler eventHandler,
@Nullable VideoRendererEventListener eventListener, int maxDroppedFramesToNotify) { @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.allowedJoiningTimeMs = allowedJoiningTimeMs;
this.maxDroppedFramesToNotify = maxDroppedFramesToNotify; this.maxDroppedFramesToNotify = maxDroppedFramesToNotify;
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
...@@ -446,14 +450,21 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -446,14 +450,21 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
@Override @Override
protected void configureCodec(MediaCodecInfo codecInfo, protected void configureCodec(
MediaCodec codec, MediaCodecInfo codecInfo,
Format format, MediaCodec codec,
MediaCrypto crypto, Format format,
float codecOperatingRate) throws DecoderQueryException { MediaCrypto crypto,
float codecOperatingRate)
throws DecoderQueryException {
codecMaxValues = getCodecMaxValues(codecInfo, format, getStreamFormats()); codecMaxValues = getCodecMaxValues(codecInfo, format, getStreamFormats());
MediaFormat mediaFormat = getMediaFormat(format, codecMaxValues, deviceNeedsAutoFrcWorkaround, MediaFormat mediaFormat =
tunnelingAudioSessionId, codecOperatingRate); getMediaFormat(
format,
codecMaxValues,
codecOperatingRate,
deviceNeedsAutoFrcWorkaround,
tunnelingAudioSessionId);
if (surface == null) { if (surface == null) {
Assertions.checkState(shouldUseDummySurface(codecInfo)); Assertions.checkState(shouldUseDummySurface(codecInfo));
if (dummySurface == null) { if (dummySurface == null) {
...@@ -505,15 +516,19 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -505,15 +516,19 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
buffersInCodecCount = 0; buffersInCodecCount = 0;
} }
@TargetApi(23)
@Override @Override
protected void updateCodecOperatingRate(MediaCodec codec, Format format, float codecOperatingRate) { protected float getCodecOperatingRate(
if (format.frameRate == Format.NO_VALUE) { float operatingRate, Format format, Format[] streamFormats) {
return; // 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(); return maxFrameRate == -1 ? CODEC_OPERATING_RATE_UNSET : (maxFrameRate * operatingRate);
codecParameters.putFloat(MediaFormat.KEY_OPERATING_RATE, format.frameRate * codecOperatingRate);
codec.setParameters(codecParameters);
} }
@Override @Override
...@@ -951,20 +966,21 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -951,20 +966,21 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
* *
* @param format The format of media. * @param format The format of media.
* @param codecMaxValues Codec max values that should be used when configuring the decoder. * @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 * @param deviceNeedsAutoFrcWorkaround Whether the device is known to enable frame-rate conversion
* logic that negatively impacts ExoPlayer. * logic that negatively impacts ExoPlayer.
* @param tunnelingAudioSessionId The audio session id to use for tunneling, or {@link * @param tunnelingAudioSessionId The audio session id to use for tunneling, or {@link
* C#AUDIO_SESSION_ID_UNSET} if tunneling should not be enabled. * 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. * @return The framework {@link MediaFormat} that should be used to configure the decoder.
*/ */
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
protected MediaFormat getMediaFormat( protected MediaFormat getMediaFormat(
Format format, Format format,
CodecMaxValues codecMaxValues, CodecMaxValues codecMaxValues,
float codecOperatingRate,
boolean deviceNeedsAutoFrcWorkaround, boolean deviceNeedsAutoFrcWorkaround,
int tunnelingAudioSessionId, int tunnelingAudioSessionId) {
float codecOperatingRate) {
MediaFormat mediaFormat = new MediaFormat(); MediaFormat mediaFormat = new MediaFormat();
// Set format parameters that should always be set. // Set format parameters that should always be set.
mediaFormat.setString(MediaFormat.KEY_MIME, format.sampleMimeType); mediaFormat.setString(MediaFormat.KEY_MIME, format.sampleMimeType);
...@@ -983,8 +999,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -983,8 +999,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
// Set codec configuration values. // Set codec configuration values.
if (Util.SDK_INT >= 23) { if (Util.SDK_INT >= 23) {
mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, 0 /* realtime priority */); mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, 0 /* realtime priority */);
if (format.frameRate != Format.NO_VALUE) { if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET) {
mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate * format.frameRate); mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate);
} }
} }
if (deviceNeedsAutoFrcWorkaround) { if (deviceNeedsAutoFrcWorkaround) {
......
...@@ -81,15 +81,20 @@ public class DebugRenderersFactory extends DefaultRenderersFactory { ...@@ -81,15 +81,20 @@ public class DebugRenderersFactory extends DefaultRenderersFactory {
} }
@Override @Override
protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format, protected void configureCodec(
MediaCrypto crypto, float codecOperatingRate) throws DecoderQueryException { 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 // 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 // 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 // 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) // dropped frames allowed, this is not desired behavior. Hence we skip (rather than drop)
// frames up to the current playback position [Internal: b/66494991]. // frames up to the current playback position [Internal: b/66494991].
skipToPositionBeforeRenderingFirstFrame = getState() == Renderer.STATE_STARTED; skipToPositionBeforeRenderingFirstFrame = getState() == Renderer.STATE_STARTED;
super.configureCodec(codecInfo, codec, format, crypto, codecOperatingRate); super.configureCodec(codecInfo, codec, format, crypto, operatingRate);
} }
@Override @Override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment