Commit badd9356 by Oliver Woodman

Refine use of KEY_OPERATING_RATE

parent f4219b55
......@@ -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
......
......@@ -137,11 +137,6 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
}
@Override
public final void setOperatingRate(float operatingRate) {
onOperatingRateChanged(operatingRate);
}
@Override
public final void stop() throws ExoPlaybackException {
Assertions.checkState(state == STATE_STARTED);
state = STATE_ENABLED;
......@@ -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.
* <p>
* The default implementation is a no-op.
......
......@@ -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();
......
......@@ -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.
* <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}.
......
......@@ -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.
......@@ -351,6 +361,14 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
@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) {
eventDispatcher.decoderInitialized(name, initializedTimestampMs, 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;
......
......@@ -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<FrameworkMediaCrypto> 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) {
......
......@@ -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
......
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