Commit cdd92450 by tofunmi Committed by christosts

Fix InternalTextureManager so that all frames are sent downstream

PiperOrigin-RevId: 509478455
parent 08cf6db3
...@@ -145,7 +145,8 @@ public final class GlEffectsFrameProcessorPixelTest { ...@@ -145,7 +145,8 @@ public final class GlEffectsFrameProcessorPixelTest {
assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE); assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
} }
// TODO(b/262693274): Once texture deletion is added to InternalTextureManager.java, add a test // TODO(b/262693274): Once texture deletion is added to InternalTextureManager.java, add a test
// queuing multiple input bitmaps to ensure successfully completion without errors. // queuing multiple input bitmaps to ensure successfully completion without errors, ensuring the
// correct number of frames haas been queued.
@Test @Test
public void noEffects_withFrameCache_matchesGoldenFile() throws Exception { public void noEffects_withFrameCache_matchesGoldenFile() throws Exception {
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
package com.google.android.exoplayer2.effect; package com.google.android.exoplayer2.effect;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static java.lang.Math.round; import static java.lang.Math.floor;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.opengl.GLES20; import android.opengl.GLES20;
...@@ -29,19 +29,31 @@ import com.google.android.exoplayer2.util.GlUtil; ...@@ -29,19 +29,31 @@ import com.google.android.exoplayer2.util.GlUtil;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
/** Forwards a frame produced from a {@link Bitmap} to a {@link GlShaderProgram} for consumption. */ /**
* Forwards a frame produced from a {@link Bitmap} to a {@link GlShaderProgram} for consumption
*
* <p>Methods in this class can be called from any thread.
*/
/* package */ class InternalTextureManager implements GlShaderProgram.InputListener { /* package */ class InternalTextureManager implements GlShaderProgram.InputListener {
private final GlShaderProgram shaderProgram; private final GlShaderProgram shaderProgram;
private final FrameProcessingTaskExecutor frameProcessingTaskExecutor; private final FrameProcessingTaskExecutor frameProcessingTaskExecutor;
// The queue holds all bitmaps with one or more frames pending to be sent downstream.
private final Queue<BitmapFrameSequenceInfo> pendingBitmaps; private final Queue<BitmapFrameSequenceInfo> pendingBitmaps;
private int downstreamShaderProgramCapacity; private int downstreamShaderProgramCapacity;
private int availableFrameCount; private int framesToQueueForCurrentBitmap;
private long currentPresentationTimeUs; private long currentPresentationTimeUs;
private long totalDurationUs;
private boolean inputEnded; private boolean inputEnded;
private boolean outputEnded;
/**
* Creates a new instance.
*
* @param shaderProgram The {@link GlShaderProgram} for which this {@code InternalTextureManager}
* will be set as the {@link GlShaderProgram.InputListener}.
* @param frameProcessingTaskExecutor The {@link FrameProcessingTaskExecutor} that the methods of
* this class run on.
*/
public InternalTextureManager( public InternalTextureManager(
GlShaderProgram shaderProgram, FrameProcessingTaskExecutor frameProcessingTaskExecutor) { GlShaderProgram shaderProgram, FrameProcessingTaskExecutor frameProcessingTaskExecutor) {
this.shaderProgram = shaderProgram; this.shaderProgram = shaderProgram;
...@@ -51,6 +63,10 @@ import java.util.concurrent.LinkedBlockingQueue; ...@@ -51,6 +63,10 @@ import java.util.concurrent.LinkedBlockingQueue;
@Override @Override
public void onReadyToAcceptInputFrame() { public void onReadyToAcceptInputFrame() {
// TODO(b/262693274): Delete texture when last duplicate of the frame comes back from the shader
// program and change to only allocate one texId at a time. A change to the
// onInputFrameProcessed() method signature to include presentationTimeUs will probably be
// needed to do this.
frameProcessingTaskExecutor.submit( frameProcessingTaskExecutor.submit(
() -> { () -> {
downstreamShaderProgramCapacity++; downstreamShaderProgramCapacity++;
...@@ -58,84 +74,90 @@ import java.util.concurrent.LinkedBlockingQueue; ...@@ -58,84 +74,90 @@ import java.util.concurrent.LinkedBlockingQueue;
}); });
} }
@Override /**
public void onInputFrameProcessed(TextureInfo inputTexture) { * Provides an input {@link Bitmap} to put into the video frames.
// TODO(b/262693274): Delete texture when last duplicate of the frame comes back from the shader *
// program and change to only allocate one texId at a time. A change to method signature to * @see FrameProcessor#queueInputBitmap
// include presentationTimeUs will probably be needed to do this. */
frameProcessingTaskExecutor.submit(
() -> {
if (availableFrameCount == 0) {
signalEndOfInput();
}
});
}
public void queueInputBitmap( public void queueInputBitmap(
Bitmap inputBitmap, long durationUs, float frameRate, boolean useHdr) { Bitmap inputBitmap, long durationUs, float frameRate, boolean useHdr) {
frameProcessingTaskExecutor.submit( frameProcessingTaskExecutor.submit(
() -> setupBitmap(inputBitmap, durationUs, frameRate, useHdr)); () -> setupBitmap(inputBitmap, durationUs, frameRate, useHdr));
} }
/**
* Signals the end of the input.
*
* @see FrameProcessor#signalEndOfInput()
*/
public void signalEndOfInput() {
frameProcessingTaskExecutor.submit(
() -> {
inputEnded = true;
signalEndOfOutput();
});
}
@WorkerThread @WorkerThread
private void setupBitmap(Bitmap bitmap, long durationUs, float frameRate, boolean useHdr) private void setupBitmap(Bitmap bitmap, long durationUs, float frameRate, boolean useHdr)
throws FrameProcessingException { throws FrameProcessingException {
if (inputEnded) { if (inputEnded) {
return; return;
} }
int bitmapTexId;
try { try {
int bitmapTexId = bitmapTexId =
GlUtil.createTexture( GlUtil.createTexture(
bitmap.getWidth(), bitmap.getHeight(), /* useHighPrecisionColorComponents= */ useHdr); bitmap.getWidth(), bitmap.getHeight(), /* useHighPrecisionColorComponents= */ useHdr);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, bitmapTexId); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, bitmapTexId);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, bitmap, /* border= */ 0); GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, bitmap, /* border= */ 0);
GlUtil.checkGlError(); GlUtil.checkGlError();
TextureInfo textureInfo =
new TextureInfo(
bitmapTexId, /* fboId= */ C.INDEX_UNSET, bitmap.getWidth(), bitmap.getHeight());
int timeIncrementUs = round(C.MICROS_PER_SECOND / frameRate);
availableFrameCount += round((frameRate * durationUs) / C.MICROS_PER_SECOND);
totalDurationUs += durationUs;
pendingBitmaps.add(
new BitmapFrameSequenceInfo(textureInfo, timeIncrementUs, totalDurationUs));
} catch (GlUtil.GlException e) { } catch (GlUtil.GlException e) {
throw FrameProcessingException.from(e); throw FrameProcessingException.from(e);
} }
TextureInfo textureInfo =
new TextureInfo(
bitmapTexId, /* fboId= */ C.INDEX_UNSET, bitmap.getWidth(), bitmap.getHeight());
int framesToAdd = (int) floor(frameRate * (durationUs / (float) C.MICROS_PER_SECOND));
long frameDurationUs = (long) floor(C.MICROS_PER_SECOND / frameRate);
pendingBitmaps.add(new BitmapFrameSequenceInfo(textureInfo, frameDurationUs, framesToAdd));
maybeQueueToShaderProgram(); maybeQueueToShaderProgram();
} }
@WorkerThread @WorkerThread
private void maybeQueueToShaderProgram() { private void maybeQueueToShaderProgram() {
if (inputEnded || availableFrameCount == 0 || downstreamShaderProgramCapacity == 0) { if (pendingBitmaps.isEmpty() || downstreamShaderProgramCapacity == 0) {
return; return;
} }
availableFrameCount--;
BitmapFrameSequenceInfo currentBitmap = checkNotNull(pendingBitmaps.peek());
if (framesToQueueForCurrentBitmap == 0) {
framesToQueueForCurrentBitmap = currentBitmap.numberOfFrames;
}
framesToQueueForCurrentBitmap--;
downstreamShaderProgramCapacity--; downstreamShaderProgramCapacity--;
BitmapFrameSequenceInfo currentFrame = checkNotNull(pendingBitmaps.peek()); shaderProgram.queueInputFrame(currentBitmap.textureInfo, currentPresentationTimeUs);
shaderProgram.queueInputFrame(currentFrame.textureInfo, currentPresentationTimeUs);
currentPresentationTimeUs += currentFrame.timeIncrementUs; currentPresentationTimeUs += currentBitmap.frameDurationUs;
if (currentPresentationTimeUs >= currentFrame.endPresentationTimeUs) { if (framesToQueueForCurrentBitmap == 0) {
pendingBitmaps.remove(); pendingBitmaps.remove();
signalEndOfOutput();
} }
} }
/** @WorkerThread
* Signals the end of the input. private void signalEndOfOutput() {
* if (framesToQueueForCurrentBitmap == 0
* @see FrameProcessor#signalEndOfInput() && pendingBitmaps.isEmpty()
*/ && inputEnded
public void signalEndOfInput() { && !outputEnded) {
frameProcessingTaskExecutor.submit( shaderProgram.signalEndOfCurrentInputStream();
() -> { outputEnded = true;
if (inputEnded) { }
return;
}
inputEnded = true;
shaderProgram.signalEndOfCurrentInputStream();
});
} }
/** /**
...@@ -144,14 +166,14 @@ import java.util.concurrent.LinkedBlockingQueue; ...@@ -144,14 +166,14 @@ import java.util.concurrent.LinkedBlockingQueue;
*/ */
private static final class BitmapFrameSequenceInfo { private static final class BitmapFrameSequenceInfo {
public final TextureInfo textureInfo; public final TextureInfo textureInfo;
public final long timeIncrementUs; public final long frameDurationUs;
public final long endPresentationTimeUs; public final int numberOfFrames;
public BitmapFrameSequenceInfo( public BitmapFrameSequenceInfo(
TextureInfo textureInfo, long timeIncrementUs, long endPresentationTimeUs) { TextureInfo textureInfo, long frameDurationUs, int numberOfFrames) {
this.textureInfo = textureInfo; this.textureInfo = textureInfo;
this.timeIncrementUs = timeIncrementUs; this.frameDurationUs = frameDurationUs;
this.endPresentationTimeUs = endPresentationTimeUs; this.numberOfFrames = numberOfFrames;
} }
} }
} }
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