mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Add frame cache to support replay frames
PiperOrigin-RevId: 737727035
This commit is contained in:
parent
059cb23f3d
commit
9ac58fa405
@ -280,6 +280,8 @@ public interface VideoFrameProcessor {
|
|||||||
/**
|
/**
|
||||||
* Updates an {@linkplain Listener#onOutputFrameAvailableForRendering available frame} with the
|
* Updates an {@linkplain Listener#onOutputFrameAvailableForRendering available frame} with the
|
||||||
* modified effects.
|
* modified effects.
|
||||||
|
*
|
||||||
|
* <p>This method can be called from any thread.
|
||||||
*/
|
*/
|
||||||
void redraw();
|
void redraw();
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
|
|||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||||
import static androidx.media3.common.util.GlUtil.getDefaultEglDisplay;
|
import static androidx.media3.common.util.GlUtil.getDefaultEglDisplay;
|
||||||
|
import static androidx.media3.common.util.Util.castNonNull;
|
||||||
import static androidx.media3.effect.DebugTraceUtil.COMPONENT_VFP;
|
import static androidx.media3.effect.DebugTraceUtil.COMPONENT_VFP;
|
||||||
import static androidx.media3.effect.DebugTraceUtil.EVENT_RECEIVE_END_OF_ALL_INPUT;
|
import static androidx.media3.effect.DebugTraceUtil.EVENT_RECEIVE_END_OF_ALL_INPUT;
|
||||||
import static androidx.media3.effect.DebugTraceUtil.EVENT_REGISTER_NEW_INPUT_STREAM;
|
import static androidx.media3.effect.DebugTraceUtil.EVENT_REGISTER_NEW_INPUT_STREAM;
|
||||||
@ -157,6 +158,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
private @MonotonicNonNull GlObjectsProvider glObjectsProvider;
|
private @MonotonicNonNull GlObjectsProvider glObjectsProvider;
|
||||||
private GlTextureProducer.@MonotonicNonNull Listener textureOutputListener;
|
private GlTextureProducer.@MonotonicNonNull Listener textureOutputListener;
|
||||||
private int textureOutputCapacity;
|
private int textureOutputCapacity;
|
||||||
|
private boolean enableReplayableCache;
|
||||||
private boolean requireRegisteringAllInputFrames;
|
private boolean requireRegisteringAllInputFrames;
|
||||||
private boolean experimentalAdjustSurfaceTextureTransformationMatrix;
|
private boolean experimentalAdjustSurfaceTextureTransformationMatrix;
|
||||||
private boolean experimentalRepeatInputBitmapWithoutResampling;
|
private boolean experimentalRepeatInputBitmapWithoutResampling;
|
||||||
@ -175,6 +177,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
glObjectsProvider = factory.glObjectsProvider;
|
glObjectsProvider = factory.glObjectsProvider;
|
||||||
textureOutputListener = factory.textureOutputListener;
|
textureOutputListener = factory.textureOutputListener;
|
||||||
textureOutputCapacity = factory.textureOutputCapacity;
|
textureOutputCapacity = factory.textureOutputCapacity;
|
||||||
|
enableReplayableCache = factory.enableReplayableCache;
|
||||||
requireRegisteringAllInputFrames = !factory.repeatLastRegisteredFrame;
|
requireRegisteringAllInputFrames = !factory.repeatLastRegisteredFrame;
|
||||||
experimentalAdjustSurfaceTextureTransformationMatrix =
|
experimentalAdjustSurfaceTextureTransformationMatrix =
|
||||||
factory.experimentalAdjustSurfaceTextureTransformationMatrix;
|
factory.experimentalAdjustSurfaceTextureTransformationMatrix;
|
||||||
@ -265,6 +268,22 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether to use a frame cache to {@link DefaultVideoFrameProcessor#redraw} frames.
|
||||||
|
*
|
||||||
|
* <p>The default value is {@code false}, in this case calling {@link
|
||||||
|
* VideoFrameProcessor#redraw} throws {@link UnsupportedOperationException}.
|
||||||
|
*
|
||||||
|
* <p>Using a frame cache enables precise redrawing, but increases resource and power usages.
|
||||||
|
*
|
||||||
|
* @param enableReplayableCache Whether to use a frame cache.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setEnableReplayableCache(boolean enableReplayableCache) {
|
||||||
|
this.enableReplayableCache = enableReplayableCache;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets texture output settings.
|
* Sets texture output settings.
|
||||||
*
|
*
|
||||||
@ -340,6 +359,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
executorService,
|
executorService,
|
||||||
textureOutputListener,
|
textureOutputListener,
|
||||||
textureOutputCapacity,
|
textureOutputCapacity,
|
||||||
|
enableReplayableCache,
|
||||||
experimentalAdjustSurfaceTextureTransformationMatrix,
|
experimentalAdjustSurfaceTextureTransformationMatrix,
|
||||||
experimentalRepeatInputBitmapWithoutResampling);
|
experimentalRepeatInputBitmapWithoutResampling);
|
||||||
}
|
}
|
||||||
@ -351,6 +371,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
@Nullable private final ExecutorService executorService;
|
@Nullable private final ExecutorService executorService;
|
||||||
@Nullable private final GlTextureProducer.Listener textureOutputListener;
|
@Nullable private final GlTextureProducer.Listener textureOutputListener;
|
||||||
private final int textureOutputCapacity;
|
private final int textureOutputCapacity;
|
||||||
|
private final boolean enableReplayableCache;
|
||||||
private final boolean experimentalAdjustSurfaceTextureTransformationMatrix;
|
private final boolean experimentalAdjustSurfaceTextureTransformationMatrix;
|
||||||
private final boolean experimentalRepeatInputBitmapWithoutResampling;
|
private final boolean experimentalRepeatInputBitmapWithoutResampling;
|
||||||
|
|
||||||
@ -361,6 +382,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
@Nullable ExecutorService executorService,
|
@Nullable ExecutorService executorService,
|
||||||
@Nullable GlTextureProducer.Listener textureOutputListener,
|
@Nullable GlTextureProducer.Listener textureOutputListener,
|
||||||
int textureOutputCapacity,
|
int textureOutputCapacity,
|
||||||
|
boolean enableReplayableCache,
|
||||||
boolean experimentalAdjustSurfaceTextureTransformationMatrix,
|
boolean experimentalAdjustSurfaceTextureTransformationMatrix,
|
||||||
boolean experimentalRepeatInputBitmapWithoutResampling) {
|
boolean experimentalRepeatInputBitmapWithoutResampling) {
|
||||||
this.sdrWorkingColorSpace = sdrWorkingColorSpace;
|
this.sdrWorkingColorSpace = sdrWorkingColorSpace;
|
||||||
@ -369,6 +391,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
this.executorService = executorService;
|
this.executorService = executorService;
|
||||||
this.textureOutputListener = textureOutputListener;
|
this.textureOutputListener = textureOutputListener;
|
||||||
this.textureOutputCapacity = textureOutputCapacity;
|
this.textureOutputCapacity = textureOutputCapacity;
|
||||||
|
this.enableReplayableCache = enableReplayableCache;
|
||||||
this.experimentalAdjustSurfaceTextureTransformationMatrix =
|
this.experimentalAdjustSurfaceTextureTransformationMatrix =
|
||||||
experimentalAdjustSurfaceTextureTransformationMatrix;
|
experimentalAdjustSurfaceTextureTransformationMatrix;
|
||||||
this.experimentalRepeatInputBitmapWithoutResampling =
|
this.experimentalRepeatInputBitmapWithoutResampling =
|
||||||
@ -437,6 +460,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
listener,
|
listener,
|
||||||
instanceGlObjectsProvider,
|
instanceGlObjectsProvider,
|
||||||
shouldReleaseGlObjectsProvider,
|
shouldReleaseGlObjectsProvider,
|
||||||
|
enableReplayableCache,
|
||||||
textureOutputListener,
|
textureOutputListener,
|
||||||
textureOutputCapacity,
|
textureOutputCapacity,
|
||||||
repeatLastRegisteredFrame,
|
repeatLastRegisteredFrame,
|
||||||
@ -492,6 +516,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
private final Object lock;
|
private final Object lock;
|
||||||
private final ColorInfo outputColorInfo;
|
private final ColorInfo outputColorInfo;
|
||||||
private final DebugViewProvider debugViewProvider;
|
private final DebugViewProvider debugViewProvider;
|
||||||
|
@Nullable private final ReplayableFrameCacheGlShaderProgram frameCache;
|
||||||
|
|
||||||
private volatile @MonotonicNonNull FrameInfo nextInputFrameInfo;
|
private volatile @MonotonicNonNull FrameInfo nextInputFrameInfo;
|
||||||
private volatile boolean inputStreamEnded;
|
private volatile boolean inputStreamEnded;
|
||||||
@ -508,7 +533,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
FinalShaderProgramWrapper finalShaderProgramWrapper,
|
FinalShaderProgramWrapper finalShaderProgramWrapper,
|
||||||
boolean renderFramesAutomatically,
|
boolean renderFramesAutomatically,
|
||||||
ColorInfo outputColorInfo,
|
ColorInfo outputColorInfo,
|
||||||
DebugViewProvider debugViewProvider) {
|
DebugViewProvider debugViewProvider,
|
||||||
|
@Nullable ReplayableFrameCacheGlShaderProgram frameCache) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.glObjectsProvider = glObjectsProvider;
|
this.glObjectsProvider = glObjectsProvider;
|
||||||
this.shouldReleaseGlObjectsProvider = shouldReleaseGlObjectsProvider;
|
this.shouldReleaseGlObjectsProvider = shouldReleaseGlObjectsProvider;
|
||||||
@ -521,18 +547,30 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
this.activeEffects = new ArrayList<>();
|
this.activeEffects = new ArrayList<>();
|
||||||
this.lock = new Object();
|
this.lock = new Object();
|
||||||
this.outputColorInfo = outputColorInfo;
|
this.outputColorInfo = outputColorInfo;
|
||||||
|
this.frameCache = frameCache;
|
||||||
this.debugViewProvider = debugViewProvider;
|
this.debugViewProvider = debugViewProvider;
|
||||||
this.finalShaderProgramWrapper = finalShaderProgramWrapper;
|
this.finalShaderProgramWrapper = finalShaderProgramWrapper;
|
||||||
this.intermediateGlShaderPrograms = new ArrayList<>();
|
this.intermediateGlShaderPrograms = new ArrayList<>();
|
||||||
this.inputStreamRegisteredCondition = new ConditionVariable();
|
this.inputStreamRegisteredCondition = new ConditionVariable();
|
||||||
inputStreamRegisteredCondition.open();
|
inputStreamRegisteredCondition.open();
|
||||||
this.finalShaderProgramWrapper.setListener(
|
this.finalShaderProgramWrapper.setListener(
|
||||||
() -> {
|
new FinalShaderProgramWrapper.Listener() {
|
||||||
if (inputStreamEnded) {
|
@Override
|
||||||
listenerExecutor.execute(listener::onEnded);
|
public void onInputStreamProcessed() {
|
||||||
DebugTraceUtil.logEvent(COMPONENT_VFP, EVENT_SIGNAL_ENDED, C.TIME_END_OF_SOURCE);
|
if (inputStreamEnded) {
|
||||||
} else {
|
listenerExecutor.execute(listener::onEnded);
|
||||||
submitPendingInputStream();
|
DebugTraceUtil.logEvent(COMPONENT_VFP, EVENT_SIGNAL_ENDED, C.TIME_END_OF_SOURCE);
|
||||||
|
} else {
|
||||||
|
DefaultVideoFrameProcessor.this.submitPendingInputStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFrameRendered(long presentationTimeUs) {
|
||||||
|
if (frameCache == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frameCache.onFrameRendered(presentationTimeUs);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -624,9 +662,31 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
return inputSwitcher.getInputSurface();
|
return inputSwitcher.getInputSurface();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>{@code DefaultVideoFrameProcessor} keeps track of the redraw requests received. If a call to
|
||||||
|
* redraw is made when another redraw request is ongoing, the new request will be performed later
|
||||||
|
* when the ongoing redraw completes, and this method will return immediately.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void redraw() {
|
public void redraw() {
|
||||||
throw new UnsupportedOperationException();
|
if (frameCache == null) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"Replaying when enableReplayableCache is set to false");
|
||||||
|
}
|
||||||
|
// TODO: b/391109644 - Call listener method in VideoFrameMetadataListener and debounce
|
||||||
|
// accordingly.
|
||||||
|
if (frameCache.isEmpty()) {
|
||||||
|
// Don't redraw right after flush, because the frame cache is also be flushed and it's empty.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
videoFrameProcessingTaskExecutor.submit(
|
||||||
|
() -> {
|
||||||
|
finalShaderProgramWrapper.prepareToRedraw(
|
||||||
|
castNonNull(frameCache).getReplayFramePresentationTimeUs());
|
||||||
|
frameCache.replayFrame();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -853,6 +913,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
Listener listener,
|
Listener listener,
|
||||||
GlObjectsProvider glObjectsProvider,
|
GlObjectsProvider glObjectsProvider,
|
||||||
boolean shouldReleaseGlObjectsProvider,
|
boolean shouldReleaseGlObjectsProvider,
|
||||||
|
boolean enableReplayableCache,
|
||||||
@Nullable GlTextureProducer.Listener textureOutputListener,
|
@Nullable GlTextureProducer.Listener textureOutputListener,
|
||||||
int textureOutputCapacity,
|
int textureOutputCapacity,
|
||||||
boolean repeatLastRegisteredFrame,
|
boolean repeatLastRegisteredFrame,
|
||||||
@ -860,8 +921,9 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
boolean experimentalRepeatInputBitmapWithoutResampling)
|
boolean experimentalRepeatInputBitmapWithoutResampling)
|
||||||
throws GlUtil.GlException, VideoFrameProcessingException {
|
throws GlUtil.GlException, VideoFrameProcessingException {
|
||||||
EGLDisplay eglDisplay = getDefaultEglDisplay();
|
EGLDisplay eglDisplay = getDefaultEglDisplay();
|
||||||
|
boolean isOutputTransferHdr = ColorInfo.isTransferHdr(outputColorInfo);
|
||||||
int[] configAttributes =
|
int[] configAttributes =
|
||||||
ColorInfo.isTransferHdr(outputColorInfo)
|
isOutputTransferHdr
|
||||||
? GlUtil.EGL_CONFIG_ATTRIBUTES_RGBA_1010102
|
? GlUtil.EGL_CONFIG_ATTRIBUTES_RGBA_1010102
|
||||||
: GlUtil.EGL_CONFIG_ATTRIBUTES_RGBA_8888;
|
: GlUtil.EGL_CONFIG_ATTRIBUTES_RGBA_8888;
|
||||||
Pair<EGLContext, EGLSurface> eglContextAndPlaceholderSurface =
|
Pair<EGLContext, EGLSurface> eglContextAndPlaceholderSurface =
|
||||||
@ -874,7 +936,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
.setHdrStaticInfo(null)
|
.setHdrStaticInfo(null)
|
||||||
.build();
|
.build();
|
||||||
ColorInfo intermediateColorInfo =
|
ColorInfo intermediateColorInfo =
|
||||||
ColorInfo.isTransferHdr(outputColorInfo)
|
isOutputTransferHdr
|
||||||
? linearColorInfo
|
? linearColorInfo
|
||||||
: sdrWorkingColorSpace == WORKING_COLOR_SPACE_LINEAR
|
: sdrWorkingColorSpace == WORKING_COLOR_SPACE_LINEAR
|
||||||
? linearColorInfo
|
? linearColorInfo
|
||||||
@ -919,7 +981,10 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
finalShaderProgramWrapper,
|
finalShaderProgramWrapper,
|
||||||
renderFramesAutomatically,
|
renderFramesAutomatically,
|
||||||
outputColorInfo,
|
outputColorInfo,
|
||||||
debugViewProvider);
|
debugViewProvider,
|
||||||
|
enableReplayableCache
|
||||||
|
? new ReplayableFrameCacheGlShaderProgram(context, /* useHdr= */ isOutputTransferHdr)
|
||||||
|
: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1042,18 +1107,25 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
|
|
||||||
if (forceReconfigure || !activeEffects.equals(inputStreamInfo.effects)) {
|
if (forceReconfigure || !activeEffects.equals(inputStreamInfo.effects)) {
|
||||||
if (!intermediateGlShaderPrograms.isEmpty()) {
|
if (!intermediateGlShaderPrograms.isEmpty()) {
|
||||||
for (int i = 0; i < intermediateGlShaderPrograms.size(); i++) {
|
// If frameCache is present, it's the first item in the list, skip releasing it.
|
||||||
|
int startIndex = frameCache == null ? 0 : 1;
|
||||||
|
for (int i = startIndex; i < intermediateGlShaderPrograms.size(); i++) {
|
||||||
intermediateGlShaderPrograms.get(i).release();
|
intermediateGlShaderPrograms.get(i).release();
|
||||||
}
|
}
|
||||||
intermediateGlShaderPrograms.clear();
|
intermediateGlShaderPrograms.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (frameCache != null) {
|
||||||
|
intermediateGlShaderPrograms.add(frameCache);
|
||||||
|
}
|
||||||
|
|
||||||
ImmutableList.Builder<Effect> effectsListBuilder =
|
ImmutableList.Builder<Effect> effectsListBuilder =
|
||||||
new ImmutableList.Builder<Effect>().addAll(inputStreamInfo.effects);
|
new ImmutableList.Builder<Effect>().addAll(inputStreamInfo.effects);
|
||||||
if (debugViewProvider != DebugViewProvider.NONE) {
|
if (debugViewProvider != DebugViewProvider.NONE) {
|
||||||
effectsListBuilder.add(new DebugViewEffect(debugViewProvider, outputColorInfo));
|
effectsListBuilder.add(new DebugViewEffect(debugViewProvider, outputColorInfo));
|
||||||
}
|
}
|
||||||
// The GlShaderPrograms that should be inserted in between InputSwitcher and
|
|
||||||
|
// The GlShaderPrograms that should be inserted in between the frame cache and
|
||||||
// FinalShaderProgramWrapper.
|
// FinalShaderProgramWrapper.
|
||||||
intermediateGlShaderPrograms.addAll(
|
intermediateGlShaderPrograms.addAll(
|
||||||
createGlShaderPrograms(
|
createGlShaderPrograms(
|
||||||
|
@ -41,6 +41,7 @@ import androidx.media3.common.util.GlUtil;
|
|||||||
import androidx.media3.common.util.Log;
|
import androidx.media3.common.util.Log;
|
||||||
import androidx.media3.common.util.LongArrayQueue;
|
import androidx.media3.common.util.LongArrayQueue;
|
||||||
import androidx.media3.common.util.Size;
|
import androidx.media3.common.util.Size;
|
||||||
|
import androidx.media3.common.util.SystemClock;
|
||||||
import androidx.media3.effect.DefaultVideoFrameProcessor.WorkingColorSpace;
|
import androidx.media3.effect.DefaultVideoFrameProcessor.WorkingColorSpace;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -72,6 +73,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
* #signalEndOfCurrentInputStream()}.
|
* #signalEndOfCurrentInputStream()}.
|
||||||
*/
|
*/
|
||||||
void onInputStreamProcessed();
|
void onInputStreamProcessed();
|
||||||
|
|
||||||
|
/** Called when a frame is rendered to the output surface. */
|
||||||
|
void onFrameRendered(long presentationTimeUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String TAG = "FinalShaderWrapper";
|
private static final String TAG = "FinalShaderWrapper";
|
||||||
@ -112,6 +116,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
private boolean outputSurfaceInfoChanged;
|
private boolean outputSurfaceInfoChanged;
|
||||||
@Nullable private SurfaceInfo outputSurfaceInfo;
|
@Nullable private SurfaceInfo outputSurfaceInfo;
|
||||||
|
|
||||||
|
private long redrawFramePresentationTimeUs;
|
||||||
|
|
||||||
/** Wraps the {@link Surface} in {@link #outputSurfaceInfo}. */
|
/** Wraps the {@link Surface} in {@link #outputSurfaceInfo}. */
|
||||||
@Nullable private EGLSurface outputEglSurface;
|
@Nullable private EGLSurface outputEglSurface;
|
||||||
|
|
||||||
@ -149,6 +155,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
outputTexturePool = new TexturePool(useHighPrecisionColorComponents, textureOutputCapacity);
|
outputTexturePool = new TexturePool(useHighPrecisionColorComponents, textureOutputCapacity);
|
||||||
outputTextureTimestamps = new LongArrayQueue(textureOutputCapacity);
|
outputTextureTimestamps = new LongArrayQueue(textureOutputCapacity);
|
||||||
syncObjects = new LongArrayQueue(textureOutputCapacity);
|
syncObjects = new LongArrayQueue(textureOutputCapacity);
|
||||||
|
redrawFramePresentationTimeUs = C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GlTextureProducer interface. Can be called on any thread.
|
// GlTextureProducer interface. Can be called on any thread.
|
||||||
@ -213,8 +220,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
public void queueInputFrame(
|
public void queueInputFrame(
|
||||||
GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs) {
|
GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs) {
|
||||||
videoFrameProcessingTaskExecutor.verifyVideoFrameProcessingThread();
|
videoFrameProcessingTaskExecutor.verifyVideoFrameProcessingThread();
|
||||||
videoFrameProcessorListenerExecutor.execute(
|
if (!isWaitingForRedrawFrame()) {
|
||||||
() -> videoFrameProcessorListener.onOutputFrameAvailableForRendering(presentationTimeUs));
|
// Don't report output available when redrawing - the redrawn frames are released immediately.
|
||||||
|
videoFrameProcessorListenerExecutor.execute(
|
||||||
|
() -> videoFrameProcessorListener.onOutputFrameAvailableForRendering(presentationTimeUs));
|
||||||
|
}
|
||||||
|
|
||||||
if (textureOutputListener == null) {
|
if (textureOutputListener == null) {
|
||||||
if (renderFramesAutomatically) {
|
if (renderFramesAutomatically) {
|
||||||
renderFrame(
|
renderFrame(
|
||||||
@ -224,6 +235,22 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
/* renderTimeNs= */ presentationTimeUs * 1000);
|
/* renderTimeNs= */ presentationTimeUs * 1000);
|
||||||
} else {
|
} else {
|
||||||
availableFrames.add(new TimedGlTextureInfo(inputTexture, presentationTimeUs));
|
availableFrames.add(new TimedGlTextureInfo(inputTexture, presentationTimeUs));
|
||||||
|
if (isWaitingForRedrawFrame()) {
|
||||||
|
if (presentationTimeUs == redrawFramePresentationTimeUs) {
|
||||||
|
redrawFramePresentationTimeUs = C.TIME_UNSET;
|
||||||
|
renderFrame(
|
||||||
|
glObjectsProvider,
|
||||||
|
inputTexture,
|
||||||
|
presentationTimeUs,
|
||||||
|
/* renderTimeNs= */ SystemClock.DEFAULT.nanoTime());
|
||||||
|
availableFrames.clear();
|
||||||
|
} else {
|
||||||
|
// Skip other frames when waiting for the replay frame to arrive, so that the producer
|
||||||
|
// can continue processing, but keep it in the availableFrames for the player to call
|
||||||
|
// renderFrame.
|
||||||
|
inputListener.onInputFrameProcessed(inputTexture);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
inputListener.onReadyToAcceptInputFrame();
|
inputListener.onReadyToAcceptInputFrame();
|
||||||
} else {
|
} else {
|
||||||
@ -309,6 +336,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
checkState(!renderFramesAutomatically);
|
checkState(!renderFramesAutomatically);
|
||||||
|
if (availableFrames.isEmpty()) {
|
||||||
|
// This only happens with redrawn frame. The available output frame notification on the player
|
||||||
|
// side runs on another thread and when redrawing rapidly, the player could receive an output
|
||||||
|
// frame from a previous redraw.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
TimedGlTextureInfo oldestAvailableFrame = availableFrames.remove();
|
TimedGlTextureInfo oldestAvailableFrame = availableFrames.remove();
|
||||||
renderFrame(
|
renderFrame(
|
||||||
glObjectsProvider,
|
glObjectsProvider,
|
||||||
@ -337,6 +371,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* package */ void prepareToRedraw(long redrawFramePresentationTimeUs) {
|
||||||
|
this.redrawFramePresentationTimeUs = redrawFramePresentationTimeUs;
|
||||||
|
for (int i = 0; i < availableFrames.size(); i++) {
|
||||||
|
TimedGlTextureInfo availableFrame = availableFrames.remove();
|
||||||
|
inputListener.onInputFrameProcessed(availableFrame.glTextureInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Must be called on the GL thread. */
|
/** Must be called on the GL thread. */
|
||||||
private void setOutputSurfaceInfoInternal(@Nullable SurfaceInfo outputSurfaceInfo) {
|
private void setOutputSurfaceInfoInternal(@Nullable SurfaceInfo outputSurfaceInfo) {
|
||||||
if (textureOutputListener != null) {
|
if (textureOutputListener != null) {
|
||||||
@ -391,6 +433,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isWaitingForRedrawFrame() {
|
||||||
|
return redrawFramePresentationTimeUs != C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
private void renderFrame(
|
private void renderFrame(
|
||||||
GlObjectsProvider glObjectsProvider,
|
GlObjectsProvider glObjectsProvider,
|
||||||
GlTextureInfo inputTexture,
|
GlTextureInfo inputTexture,
|
||||||
@ -398,8 +444,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
long renderTimeNs) {
|
long renderTimeNs) {
|
||||||
try {
|
try {
|
||||||
if (renderTimeNs == VideoFrameProcessor.DROP_OUTPUT_FRAME
|
if (renderTimeNs == VideoFrameProcessor.DROP_OUTPUT_FRAME
|
||||||
|| !ensureConfigured(glObjectsProvider, inputTexture.width, inputTexture.height)) {
|
|| !ensureConfigured(glObjectsProvider, inputTexture.width, inputTexture.height)
|
||||||
|
|| (isWaitingForRedrawFrame() && presentationTimeUs != redrawFramePresentationTimeUs)) {
|
||||||
inputListener.onInputFrameProcessed(inputTexture);
|
inputListener.onInputFrameProcessed(inputTexture);
|
||||||
|
if (renderTimeNs == VideoFrameProcessor.DROP_OUTPUT_FRAME) {
|
||||||
|
checkNotNull(listener).onFrameRendered(presentationTimeUs);
|
||||||
|
}
|
||||||
return; // Drop frames when requested, or there is no output surface and output texture.
|
return; // Drop frames when requested, or there is no output surface and output texture.
|
||||||
}
|
}
|
||||||
if (outputSurfaceInfo != null) {
|
if (outputSurfaceInfo != null) {
|
||||||
@ -445,6 +495,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
EGLExt.eglPresentationTimeANDROID(eglDisplay, outputEglSurface, eglPresentationTimeNs);
|
EGLExt.eglPresentationTimeANDROID(eglDisplay, outputEglSurface, eglPresentationTimeNs);
|
||||||
EGL14.eglSwapBuffers(eglDisplay, outputEglSurface);
|
EGL14.eglSwapBuffers(eglDisplay, outputEglSurface);
|
||||||
|
checkNotNull(listener).onFrameRendered(presentationTimeUs);
|
||||||
DebugTraceUtil.logEvent(COMPONENT_VFP, EVENT_RENDERED_TO_OUTPUT_SURFACE, presentationTimeUs);
|
DebugTraceUtil.logEvent(COMPONENT_VFP, EVENT_RENDERED_TO_OUTPUT_SURFACE, presentationTimeUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package androidx.media3.effect;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.GlObjectsProvider;
|
||||||
|
import androidx.media3.common.GlTextureInfo;
|
||||||
|
import androidx.media3.common.VideoFrameProcessingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A shader program that caches the input frames, and {@linkplain #replayFrame replays} the oldest
|
||||||
|
* input frame when instructed.
|
||||||
|
*/
|
||||||
|
/* package */ final class ReplayableFrameCacheGlShaderProgram extends FrameCacheGlShaderProgram {
|
||||||
|
private static final int CAPACITY = 2;
|
||||||
|
private static final int REPLAY_FRAME_INDEX = 0;
|
||||||
|
private static final int REGULAR_FRAME_INDEX = 1;
|
||||||
|
|
||||||
|
// Use a manually managed array to be more efficient than List add/remove methods.
|
||||||
|
private final TimedGlTextureInfo[] cachedFrames;
|
||||||
|
private int cacheSize;
|
||||||
|
|
||||||
|
public ReplayableFrameCacheGlShaderProgram(Context context, boolean useHdr)
|
||||||
|
throws VideoFrameProcessingException {
|
||||||
|
super(context, CAPACITY, useHdr);
|
||||||
|
cachedFrames = new TimedGlTextureInfo[CAPACITY];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void queueInputFrame(
|
||||||
|
GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs) {
|
||||||
|
checkState(cacheSize < CAPACITY);
|
||||||
|
super.queueInputFrame(glObjectsProvider, inputTexture, presentationTimeUs);
|
||||||
|
cachedFrames[cacheSize++] =
|
||||||
|
new TimedGlTextureInfo(
|
||||||
|
checkNotNull(outputTexturePool.getMostRecentlyUsedTexture()), presentationTimeUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseOutputFrame(GlTextureInfo outputTexture) {
|
||||||
|
// Do nothing here as this method will be called as soon as the output frame is queued into the
|
||||||
|
// subsequent shader program. This class only releases output frame based on rendering event
|
||||||
|
// from the FinalShaderProgramWrapper. See onFrameRendered().
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
cacheSize = 0;
|
||||||
|
super.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns whether there is no cached frame. */
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return cacheSize == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the presentation time of the frame that will be replayed, if {@link #replayFrame()} is
|
||||||
|
* called.
|
||||||
|
*/
|
||||||
|
public long getReplayFramePresentationTimeUs() {
|
||||||
|
if (isEmpty()) {
|
||||||
|
return C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
return cachedFrames[REPLAY_FRAME_INDEX].presentationTimeUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replays the frame from cache, with the {@linkplain #getReplayFramePresentationTimeUs replay
|
||||||
|
* timestamp}.
|
||||||
|
*/
|
||||||
|
public void replayFrame() {
|
||||||
|
if (isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the oldest frame that is queued.
|
||||||
|
TimedGlTextureInfo oldestFrame = cachedFrames[REPLAY_FRAME_INDEX];
|
||||||
|
getOutputListener()
|
||||||
|
.onOutputFrameAvailable(oldestFrame.glTextureInfo, oldestFrame.presentationTimeUs);
|
||||||
|
|
||||||
|
// Queue the subsequent frame also to keep the player's output frame queue full.
|
||||||
|
if (cacheSize > 1) {
|
||||||
|
TimedGlTextureInfo secondOldestFrame = cachedFrames[REGULAR_FRAME_INDEX];
|
||||||
|
getOutputListener()
|
||||||
|
.onOutputFrameAvailable(
|
||||||
|
secondOldestFrame.glTextureInfo, secondOldestFrame.presentationTimeUs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Removes a frame from the cache when a frame of the {@code presentationTimeUs} is rendered. */
|
||||||
|
public void onFrameRendered(long presentationTimeUs) {
|
||||||
|
// Cache needs to be full when capacity is two, only release frame n when frame n+1 is released.
|
||||||
|
if (cacheSize < CAPACITY
|
||||||
|
|| presentationTimeUs < cachedFrames[REGULAR_FRAME_INDEX].presentationTimeUs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evict the oldest frame.
|
||||||
|
TimedGlTextureInfo cachedFrame = cachedFrames[REPLAY_FRAME_INDEX];
|
||||||
|
cachedFrames[REPLAY_FRAME_INDEX] = cachedFrames[REGULAR_FRAME_INDEX];
|
||||||
|
cacheSize--;
|
||||||
|
|
||||||
|
// Release the texture, this also calls readyToAcceptInput.
|
||||||
|
super.releaseOutputFrame(cachedFrame.glTextureInfo);
|
||||||
|
}
|
||||||
|
}
|
@ -278,7 +278,7 @@ public class SingleInputVideoGraph implements VideoGraph {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void redraw() {
|
public void redraw() {
|
||||||
throw new UnsupportedOperationException();
|
checkStateNotNull(videoFrameProcessor).redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -17,18 +17,19 @@ package androidx.media3.effect;
|
|||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.GlObjectsProvider;
|
import androidx.media3.common.GlObjectsProvider;
|
||||||
import androidx.media3.common.GlTextureInfo;
|
import androidx.media3.common.GlTextureInfo;
|
||||||
import androidx.media3.common.util.GlUtil;
|
import androidx.media3.common.util.GlUtil;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Queue;
|
|
||||||
|
|
||||||
/** Holds {@code capacity} textures, to re-use textures. */
|
/** Holds {@code capacity} textures, to re-use textures. */
|
||||||
/* package */ final class TexturePool {
|
/* package */ final class TexturePool {
|
||||||
private final Queue<GlTextureInfo> freeTextures;
|
private final Deque<GlTextureInfo> freeTextures;
|
||||||
private final Queue<GlTextureInfo> inUseTextures;
|
private final Deque<GlTextureInfo> inUseTextures;
|
||||||
private final int capacity;
|
private final int capacity;
|
||||||
private final boolean useHighPrecisionColorComponents;
|
private final boolean useHighPrecisionColorComponents;
|
||||||
|
|
||||||
@ -94,6 +95,15 @@ import java.util.Queue;
|
|||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the {@link GlTextureInfo} that is most recently {@linkplain #useTexture used}. */
|
||||||
|
@Nullable
|
||||||
|
public GlTextureInfo getMostRecentlyUsedTexture() {
|
||||||
|
if (inUseTextures.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return inUseTextures.getLast();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Frees the texture represented by {@code textureInfo}.
|
* Frees the texture represented by {@code textureInfo}.
|
||||||
*
|
*
|
||||||
|
@ -78,4 +78,5 @@
|
|||||||
-dontnote androidx.media3.effect.DefaultVideoFrameProcessor$Factory$Builder
|
-dontnote androidx.media3.effect.DefaultVideoFrameProcessor$Factory$Builder
|
||||||
-keepclasseswithmembers class androidx.media3.effect.DefaultVideoFrameProcessor$Factory$Builder {
|
-keepclasseswithmembers class androidx.media3.effect.DefaultVideoFrameProcessor$Factory$Builder {
|
||||||
androidx.media3.effect.DefaultVideoFrameProcessor$Factory build();
|
androidx.media3.effect.DefaultVideoFrameProcessor$Factory build();
|
||||||
|
androidx.media3.effect.DefaultVideoFrameProcessor$Factory$Builder setEnableReplayableCache(boolean);
|
||||||
}
|
}
|
||||||
|
@ -870,9 +870,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
if (!hasSetVideoSink) {
|
if (!hasSetVideoSink) {
|
||||||
if (videoEffects != null && videoSink == null) {
|
if (videoEffects != null && videoSink == null) {
|
||||||
PlaybackVideoGraphWrapper playbackVideoGraphWrapper =
|
PlaybackVideoGraphWrapper playbackVideoGraphWrapper =
|
||||||
new PlaybackVideoGraphWrapper.Builder(context, videoFrameReleaseControl)
|
createPlaybackVideoGraphWrapper(context, videoFrameReleaseControl);
|
||||||
.setClock(getClock())
|
|
||||||
.build();
|
|
||||||
playbackVideoGraphWrapper.setTotalVideoInputCount(1);
|
playbackVideoGraphWrapper.setTotalVideoInputCount(1);
|
||||||
videoSink = playbackVideoGraphWrapper.getSink(/* inputIndex= */ 0);
|
videoSink = playbackVideoGraphWrapper.getSink(/* inputIndex= */ 0);
|
||||||
}
|
}
|
||||||
@ -946,6 +944,15 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Creates a {@link PlaybackVideoGraphWrapper} instance. */
|
||||||
|
protected PlaybackVideoGraphWrapper createPlaybackVideoGraphWrapper(
|
||||||
|
Context context, VideoFrameReleaseControl videoFrameReleaseControl) {
|
||||||
|
// TODO: b/391109644 - Add a more explicit API to enable replaying.
|
||||||
|
return new PlaybackVideoGraphWrapper.Builder(context, videoFrameReleaseControl)
|
||||||
|
.setClock(getClock())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enableMayRenderStartOfStream() {
|
public void enableMayRenderStartOfStream() {
|
||||||
if (videoSink != null) {
|
if (videoSink != null) {
|
||||||
|
@ -62,6 +62,7 @@ import java.lang.annotation.Documented;
|
|||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@ -128,6 +129,7 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
private Clock clock;
|
private Clock clock;
|
||||||
private boolean requestOpenGlToneMapping;
|
private boolean requestOpenGlToneMapping;
|
||||||
private boolean built;
|
private boolean built;
|
||||||
|
private boolean enableReplayableCache;
|
||||||
|
|
||||||
/** Creates a builder. */
|
/** Creates a builder. */
|
||||||
public Builder(Context context, VideoFrameReleaseControl videoFrameReleaseControl) {
|
public Builder(Context context, VideoFrameReleaseControl videoFrameReleaseControl) {
|
||||||
@ -222,6 +224,21 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether to enable replayable cache.
|
||||||
|
*
|
||||||
|
* <p>By default, the replayable cache is not enabled. Enable it to achieve accurate effect
|
||||||
|
* update, at the cost of using more power and computing resources.
|
||||||
|
*
|
||||||
|
* @param enableReplayableCache Whether replayable cache is enabled.
|
||||||
|
* @return This builder, for convenience.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setEnableReplayableCache(boolean enableReplayableCache) {
|
||||||
|
this.enableReplayableCache = enableReplayableCache;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the {@link PlaybackVideoGraphWrapper}.
|
* Builds the {@link PlaybackVideoGraphWrapper}.
|
||||||
*
|
*
|
||||||
@ -233,7 +250,8 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
|
|
||||||
if (videoGraphFactory == null) {
|
if (videoGraphFactory == null) {
|
||||||
if (videoFrameProcessorFactory == null) {
|
if (videoFrameProcessorFactory == null) {
|
||||||
videoFrameProcessorFactory = new ReflectiveDefaultVideoFrameProcessorFactory();
|
videoFrameProcessorFactory =
|
||||||
|
new ReflectiveDefaultVideoFrameProcessorFactory(enableReplayableCache);
|
||||||
}
|
}
|
||||||
videoGraphFactory = new ReflectiveSingleInputVideoGraphFactory(videoFrameProcessorFactory);
|
videoGraphFactory = new ReflectiveSingleInputVideoGraphFactory(videoFrameProcessorFactory);
|
||||||
}
|
}
|
||||||
@ -696,6 +714,7 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
@Override
|
@Override
|
||||||
public void redraw() {
|
public void redraw() {
|
||||||
checkState(isInitialized());
|
checkState(isInitialized());
|
||||||
|
PlaybackVideoGraphWrapper.this.flush(/* resetPosition= */ false);
|
||||||
checkNotNull(videoGraph).redraw();
|
checkNotNull(videoGraph).redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1059,29 +1078,25 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
*/
|
*/
|
||||||
private static final class ReflectiveDefaultVideoFrameProcessorFactory
|
private static final class ReflectiveDefaultVideoFrameProcessorFactory
|
||||||
implements VideoFrameProcessor.Factory {
|
implements VideoFrameProcessor.Factory {
|
||||||
private static final Supplier<VideoFrameProcessor.Factory>
|
|
||||||
VIDEO_FRAME_PROCESSOR_FACTORY_SUPPLIER =
|
private static final Supplier<Class<?>> DEFAULT_VIDEO_FRAME_PROCESSOR_FACTORY_BUILDER_CLASS =
|
||||||
Suppliers.memoize(
|
Suppliers.memoize(
|
||||||
() -> {
|
() -> {
|
||||||
try {
|
try {
|
||||||
// LINT.IfChange
|
// LINT.IfChange
|
||||||
Class<?> defaultVideoFrameProcessorFactoryBuilderClass =
|
return Class.forName(
|
||||||
Class.forName(
|
"androidx.media3.effect.DefaultVideoFrameProcessor$Factory$Builder");
|
||||||
"androidx.media3.effect.DefaultVideoFrameProcessor$Factory$Builder");
|
// LINT.ThenChange(../../../../../../../proguard-rules.txt)
|
||||||
Object builder =
|
} catch (Exception e) {
|
||||||
defaultVideoFrameProcessorFactoryBuilderClass
|
throw new IllegalStateException(e);
|
||||||
.getConstructor()
|
}
|
||||||
.newInstance();
|
});
|
||||||
return (VideoFrameProcessor.Factory)
|
|
||||||
checkNotNull(
|
private final boolean enableReplayableCache;
|
||||||
defaultVideoFrameProcessorFactoryBuilderClass
|
|
||||||
.getMethod("build")
|
public ReflectiveDefaultVideoFrameProcessorFactory(boolean enableReplayableCache) {
|
||||||
.invoke(builder));
|
this.enableReplayableCache = enableReplayableCache;
|
||||||
// LINT.ThenChange(../../../../../../../proguard-rules.txt)
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VideoFrameProcessor create(
|
public VideoFrameProcessor create(
|
||||||
@ -1092,15 +1107,31 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
Executor listenerExecutor,
|
Executor listenerExecutor,
|
||||||
VideoFrameProcessor.Listener listener)
|
VideoFrameProcessor.Listener listener)
|
||||||
throws VideoFrameProcessingException {
|
throws VideoFrameProcessingException {
|
||||||
return VIDEO_FRAME_PROCESSOR_FACTORY_SUPPLIER
|
try {
|
||||||
.get()
|
Class<?> defaultVideoFrameProcessorFactoryBuilderClass =
|
||||||
.create(
|
DEFAULT_VIDEO_FRAME_PROCESSOR_FACTORY_BUILDER_CLASS.get();
|
||||||
context,
|
Object builder =
|
||||||
debugViewProvider,
|
defaultVideoFrameProcessorFactoryBuilderClass.getConstructor().newInstance();
|
||||||
outputColorInfo,
|
Method setUseReplayableCacheMethod =
|
||||||
renderFramesAutomatically,
|
defaultVideoFrameProcessorFactoryBuilderClass.getMethod(
|
||||||
listenerExecutor,
|
"setEnableReplayableCache", boolean.class);
|
||||||
listener);
|
setUseReplayableCacheMethod.invoke(builder, enableReplayableCache);
|
||||||
|
VideoFrameProcessor.Factory factory =
|
||||||
|
(VideoFrameProcessor.Factory)
|
||||||
|
checkNotNull(
|
||||||
|
defaultVideoFrameProcessorFactoryBuilderClass
|
||||||
|
.getMethod("build")
|
||||||
|
.invoke(builder));
|
||||||
|
return factory.create(
|
||||||
|
context,
|
||||||
|
debugViewProvider,
|
||||||
|
outputColorInfo,
|
||||||
|
renderFramesAutomatically,
|
||||||
|
listenerExecutor,
|
||||||
|
listener);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new VideoFrameProcessingException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user