mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Pass appropriate frame-rate to Surface.setFrameRate
PiperOrigin-RevId: 309746009
This commit is contained in:
parent
c926acb36d
commit
862a6e4dd2
3 changed files with 92 additions and 6 deletions
|
|
@ -83,6 +83,7 @@
|
||||||
([#7247](https://github.com/google/ExoPlayer/pull/7247)).
|
([#7247](https://github.com/google/ExoPlayer/pull/7247)).
|
||||||
* Replace `CacheDataSinkFactory` and `CacheDataSourceFactory` with
|
* Replace `CacheDataSinkFactory` and `CacheDataSourceFactory` with
|
||||||
`CacheDataSink.Factory` and `CacheDataSource.Factory` respectively.
|
`CacheDataSink.Factory` and `CacheDataSource.Factory` respectively.
|
||||||
|
* Video: Pass frame rate hint to `Surface.setFrameRate` on Android R devices.
|
||||||
* Text:
|
* Text:
|
||||||
* Parse `<ruby>` and `<rt>` tags in WebVTT subtitles (rendering is coming
|
* Parse `<ruby>` and `<rt>` tags in WebVTT subtitles (rendering is coming
|
||||||
later).
|
later).
|
||||||
|
|
|
||||||
|
|
@ -374,7 +374,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
@Nullable private MediaCrypto mediaCrypto;
|
@Nullable private MediaCrypto mediaCrypto;
|
||||||
private boolean mediaCryptoRequiresSecureDecoder;
|
private boolean mediaCryptoRequiresSecureDecoder;
|
||||||
private long renderTimeLimitMs;
|
private long renderTimeLimitMs;
|
||||||
private float rendererOperatingRate;
|
private float operatingRate;
|
||||||
@Nullable private MediaCodec codec;
|
@Nullable private MediaCodec codec;
|
||||||
@Nullable private MediaCodecAdapter codecAdapter;
|
@Nullable private MediaCodecAdapter codecAdapter;
|
||||||
@Nullable private Format codecFormat;
|
@Nullable private Format codecFormat;
|
||||||
|
|
@ -447,7 +447,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
formatQueue = new TimedValueQueue<>();
|
formatQueue = new TimedValueQueue<>();
|
||||||
decodeOnlyPresentationTimestamps = new ArrayList<>();
|
decodeOnlyPresentationTimestamps = new ArrayList<>();
|
||||||
outputBufferInfo = new MediaCodec.BufferInfo();
|
outputBufferInfo = new MediaCodec.BufferInfo();
|
||||||
rendererOperatingRate = 1f;
|
operatingRate = 1f;
|
||||||
renderTimeLimitMs = C.TIME_UNSET;
|
renderTimeLimitMs = C.TIME_UNSET;
|
||||||
mediaCodecOperationMode = OPERATION_MODE_SYNCHRONOUS;
|
mediaCodecOperationMode = OPERATION_MODE_SYNCHRONOUS;
|
||||||
pendingOutputStreamOffsetsUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT];
|
pendingOutputStreamOffsetsUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT];
|
||||||
|
|
@ -710,8 +710,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void setOperatingRate(float operatingRate) throws ExoPlaybackException {
|
public void setOperatingRate(float operatingRate) throws ExoPlaybackException {
|
||||||
rendererOperatingRate = operatingRate;
|
this.operatingRate = operatingRate;
|
||||||
if (codec != null
|
if (codec != null
|
||||||
&& codecDrainAction != DRAIN_ACTION_REINITIALIZE
|
&& codecDrainAction != DRAIN_ACTION_REINITIALIZE
|
||||||
&& getState() != STATE_DISABLED) {
|
&& getState() != STATE_DISABLED) {
|
||||||
|
|
@ -1031,7 +1031,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
float codecOperatingRate =
|
float codecOperatingRate =
|
||||||
Util.SDK_INT < 23
|
Util.SDK_INT < 23
|
||||||
? CODEC_OPERATING_RATE_UNSET
|
? CODEC_OPERATING_RATE_UNSET
|
||||||
: getCodecOperatingRateV23(rendererOperatingRate, inputFormat, getStreamFormats());
|
: getCodecOperatingRateV23(operatingRate, inputFormat, getStreamFormats());
|
||||||
if (codecOperatingRate <= assumedMinimumCodecOperatingRate) {
|
if (codecOperatingRate <= assumedMinimumCodecOperatingRate) {
|
||||||
codecOperatingRate = CODEC_OPERATING_RATE_UNSET;
|
codecOperatingRate = CODEC_OPERATING_RATE_UNSET;
|
||||||
}
|
}
|
||||||
|
|
@ -1561,6 +1561,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
&& SystemClock.elapsedRealtime() < codecHotswapDeadlineMs));
|
&& SystemClock.elapsedRealtime() < codecHotswapDeadlineMs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the renderer operating rate, as set by {@link #setOperatingRate}. */
|
||||||
|
protected float getOperatingRate() {
|
||||||
|
return operatingRate;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the {@link MediaFormat#KEY_OPERATING_RATE} value for a given renderer operating rate,
|
* Returns the {@link MediaFormat#KEY_OPERATING_RATE} value for a given renderer operating rate,
|
||||||
* current {@link Format} and set of possible stream formats.
|
* current {@link Format} and set of possible stream formats.
|
||||||
|
|
@ -1589,7 +1594,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
float newCodecOperatingRate =
|
float newCodecOperatingRate =
|
||||||
getCodecOperatingRateV23(rendererOperatingRate, codecFormat, getStreamFormats());
|
getCodecOperatingRateV23(operatingRate, codecFormat, getStreamFormats());
|
||||||
if (codecOperatingRate == newCodecOperatingRate) {
|
if (codecOperatingRate == newCodecOperatingRate) {
|
||||||
// No change.
|
// No change.
|
||||||
} else if (newCodecOperatingRate == CODEC_OPERATING_RATE_UNSET) {
|
} else if (newCodecOperatingRate == CODEC_OPERATING_RATE_UNSET) {
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
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;
|
||||||
import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispatcher;
|
import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispatcher;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -98,6 +99,24 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
/** Magic frame render timestamp that indicates the EOS in tunneling mode. */
|
/** Magic frame render timestamp that indicates the EOS in tunneling mode. */
|
||||||
private static final long TUNNELING_EOS_PRESENTATION_TIME_US = Long.MAX_VALUE;
|
private static final long TUNNELING_EOS_PRESENTATION_TIME_US = Long.MAX_VALUE;
|
||||||
|
|
||||||
|
// TODO: Remove reflection once we target API level 30.
|
||||||
|
@Nullable private static final Method surfaceSetFrameRateMethod;
|
||||||
|
|
||||||
|
static {
|
||||||
|
@Nullable Method setFrameRateMethod = null;
|
||||||
|
if (Util.SDK_INT >= 30) {
|
||||||
|
try {
|
||||||
|
setFrameRateMethod = Surface.class.getMethod("setFrameRate", float.class, int.class);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
surfaceSetFrameRateMethod = setFrameRateMethod;
|
||||||
|
}
|
||||||
|
// TODO: Remove these constants and use those defined by Surface once we target API level 30.
|
||||||
|
private static final int SURFACE_FRAME_RATE_COMPATIBILITY_DEFAULT = 0;
|
||||||
|
private static final int SURFACE_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1;
|
||||||
|
|
||||||
private static boolean evaluatedDeviceNeedsSetOutputSurfaceWorkaround;
|
private static boolean evaluatedDeviceNeedsSetOutputSurfaceWorkaround;
|
||||||
private static boolean deviceNeedsSetOutputSurfaceWorkaround;
|
private static boolean deviceNeedsSetOutputSurfaceWorkaround;
|
||||||
|
|
||||||
|
|
@ -113,6 +132,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
private boolean codecHandlesHdr10PlusOutOfBandMetadata;
|
private boolean codecHandlesHdr10PlusOutOfBandMetadata;
|
||||||
|
|
||||||
private Surface surface;
|
private Surface surface;
|
||||||
|
private float surfaceFrameRate;
|
||||||
private Surface dummySurface;
|
private Surface dummySurface;
|
||||||
@VideoScalingMode private int scalingMode;
|
@VideoScalingMode private int scalingMode;
|
||||||
private boolean renderedFirstFrameAfterReset;
|
private boolean renderedFirstFrameAfterReset;
|
||||||
|
|
@ -135,6 +155,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
private int currentHeight;
|
private int currentHeight;
|
||||||
private int currentUnappliedRotationDegrees;
|
private int currentUnappliedRotationDegrees;
|
||||||
private float currentPixelWidthHeightRatio;
|
private float currentPixelWidthHeightRatio;
|
||||||
|
private float currentFrameRate;
|
||||||
private int reportedWidth;
|
private int reportedWidth;
|
||||||
private int reportedHeight;
|
private int reportedHeight;
|
||||||
private int reportedUnappliedRotationDegrees;
|
private int reportedUnappliedRotationDegrees;
|
||||||
|
|
@ -409,6 +430,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
lastRenderTimeUs = SystemClock.elapsedRealtime() * 1000;
|
lastRenderTimeUs = SystemClock.elapsedRealtime() * 1000;
|
||||||
totalVideoFrameProcessingOffsetUs = 0;
|
totalVideoFrameProcessingOffsetUs = 0;
|
||||||
videoFrameProcessingOffsetCount = 0;
|
videoFrameProcessingOffsetCount = 0;
|
||||||
|
updateSurfaceFrameRate(/* isNewSurface= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -416,6 +438,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
joiningDeadlineMs = C.TIME_UNSET;
|
joiningDeadlineMs = C.TIME_UNSET;
|
||||||
maybeNotifyDroppedFrames();
|
maybeNotifyDroppedFrames();
|
||||||
maybeNotifyVideoFrameProcessingOffset();
|
maybeNotifyVideoFrameProcessingOffset();
|
||||||
|
clearSurfaceFrameRate();
|
||||||
super.onStopped();
|
super.onStopped();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -480,7 +503,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
}
|
}
|
||||||
// We only need to update the codec if the surface has changed.
|
// We only need to update the codec if the surface has changed.
|
||||||
if (this.surface != surface) {
|
if (this.surface != surface) {
|
||||||
|
clearSurfaceFrameRate();
|
||||||
this.surface = surface;
|
this.surface = surface;
|
||||||
|
updateSurfaceFrameRate(/* isNewSurface= */ true);
|
||||||
|
|
||||||
@State int state = getState();
|
@State int state = getState();
|
||||||
MediaCodec codec = getCodec();
|
MediaCodec codec = getCodec();
|
||||||
if (codec != null) {
|
if (codec != null) {
|
||||||
|
|
@ -577,6 +603,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
buffersInCodecCount = 0;
|
buffersInCodecCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOperatingRate(float operatingRate) throws ExoPlaybackException {
|
||||||
|
super.setOperatingRate(operatingRate);
|
||||||
|
updateSurfaceFrameRate(/* isNewSurface= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected float getCodecOperatingRateV23(
|
protected float getCodecOperatingRateV23(
|
||||||
float operatingRate, Format format, Format[] streamFormats) {
|
float operatingRate, Format format, Format[] streamFormats) {
|
||||||
|
|
@ -682,6 +714,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
// On API level 20 and below the decoder does not apply the rotation.
|
// On API level 20 and below the decoder does not apply the rotation.
|
||||||
currentUnappliedRotationDegrees = outputFormat.rotationDegrees;
|
currentUnappliedRotationDegrees = outputFormat.rotationDegrees;
|
||||||
}
|
}
|
||||||
|
currentFrameRate = outputFormat.frameRate;
|
||||||
|
updateSurfaceFrameRate(/* isNewSurface= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -1049,6 +1083,52 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
maybeNotifyRenderedFirstFrame();
|
maybeNotifyRenderedFirstFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the frame-rate of the current {@link #surface} based on the renderer operating rate,
|
||||||
|
* frame-rate of the content, and whether the renderer is started.
|
||||||
|
*
|
||||||
|
* @param isNewSurface Whether the current {@link #surface} is new.
|
||||||
|
*/
|
||||||
|
private void updateSurfaceFrameRate(boolean isNewSurface) {
|
||||||
|
if (Util.SDK_INT < 30 || surface == null || surface == dummySurface) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean shouldSetFrameRate = getState() == STATE_STARTED && currentFrameRate != Format.NO_VALUE;
|
||||||
|
float surfaceFrameRate = shouldSetFrameRate ? currentFrameRate * getOperatingRate() : 0;
|
||||||
|
// We always set the frame-rate if we have a new surface, since we have no way of knowing what
|
||||||
|
// it might have been set to previously.
|
||||||
|
if (this.surfaceFrameRate == surfaceFrameRate && !isNewSurface) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.surfaceFrameRate = surfaceFrameRate;
|
||||||
|
setSurfaceFrameRateV30(surface, surfaceFrameRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clears the frame-rate of the current {@link #surface}. */
|
||||||
|
private void clearSurfaceFrameRate() {
|
||||||
|
if (Util.SDK_INT < 30 || surface == null || surface == dummySurface || surfaceFrameRate == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
surfaceFrameRate = 0;
|
||||||
|
setSurfaceFrameRateV30(surface, /* frameRate= */ 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(30)
|
||||||
|
private void setSurfaceFrameRateV30(Surface surface, float frameRate) {
|
||||||
|
if (surfaceSetFrameRateMethod == null) {
|
||||||
|
Log.e(TAG, "Failed to call Surface.setFrameRate (method does not exist)");
|
||||||
|
}
|
||||||
|
int compatibility =
|
||||||
|
frameRate == 0
|
||||||
|
? SURFACE_FRAME_RATE_COMPATIBILITY_DEFAULT
|
||||||
|
: SURFACE_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
|
||||||
|
try {
|
||||||
|
surfaceSetFrameRateMethod.invoke(surface, frameRate, compatibility);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Failed to call Surface.setFrameRate", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean shouldUseDummySurface(MediaCodecInfo codecInfo) {
|
private boolean shouldUseDummySurface(MediaCodecInfo codecInfo) {
|
||||||
return Util.SDK_INT >= 23
|
return Util.SDK_INT >= 23
|
||||||
&& !tunneling
|
&& !tunneling
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue