mirror of
https://github.com/androidx/media.git
synced 2025-05-05 06:30:24 +08:00
Fix InternalTextureManager so that all frames are sent downstream
PiperOrigin-RevId: 509478455
This commit is contained in:
parent
08cf6db305
commit
cdd924503a
@ -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 @@
|
|||||||
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;
|
|||||||
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;
|
|||||||
|
|
||||||
@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,70 +74,17 @@ 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
|
||||||
private void setupBitmap(Bitmap bitmap, long durationUs, float frameRate, boolean useHdr)
|
|
||||||
throws FrameProcessingException {
|
|
||||||
if (inputEnded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
int bitmapTexId =
|
|
||||||
GlUtil.createTexture(
|
|
||||||
bitmap.getWidth(), bitmap.getHeight(), /* useHighPrecisionColorComponents= */ useHdr);
|
|
||||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, bitmapTexId);
|
|
||||||
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, bitmap, /* border= */ 0);
|
|
||||||
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) {
|
|
||||||
throw FrameProcessingException.from(e);
|
|
||||||
}
|
|
||||||
maybeQueueToShaderProgram();
|
|
||||||
}
|
|
||||||
|
|
||||||
@WorkerThread
|
|
||||||
private void maybeQueueToShaderProgram() {
|
|
||||||
if (inputEnded || availableFrameCount == 0 || downstreamShaderProgramCapacity == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
availableFrameCount--;
|
|
||||||
downstreamShaderProgramCapacity--;
|
|
||||||
|
|
||||||
BitmapFrameSequenceInfo currentFrame = checkNotNull(pendingBitmaps.peek());
|
|
||||||
shaderProgram.queueInputFrame(currentFrame.textureInfo, currentPresentationTimeUs);
|
|
||||||
|
|
||||||
currentPresentationTimeUs += currentFrame.timeIncrementUs;
|
|
||||||
if (currentPresentationTimeUs >= currentFrame.endPresentationTimeUs) {
|
|
||||||
pendingBitmaps.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signals the end of the input.
|
* Signals the end of the input.
|
||||||
*
|
*
|
||||||
@ -130,28 +93,87 @@ import java.util.concurrent.LinkedBlockingQueue;
|
|||||||
public void signalEndOfInput() {
|
public void signalEndOfInput() {
|
||||||
frameProcessingTaskExecutor.submit(
|
frameProcessingTaskExecutor.submit(
|
||||||
() -> {
|
() -> {
|
||||||
if (inputEnded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
inputEnded = true;
|
inputEnded = true;
|
||||||
shaderProgram.signalEndOfCurrentInputStream();
|
signalEndOfOutput();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private void setupBitmap(Bitmap bitmap, long durationUs, float frameRate, boolean useHdr)
|
||||||
|
throws FrameProcessingException {
|
||||||
|
|
||||||
|
if (inputEnded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int bitmapTexId;
|
||||||
|
try {
|
||||||
|
bitmapTexId =
|
||||||
|
GlUtil.createTexture(
|
||||||
|
bitmap.getWidth(), bitmap.getHeight(), /* useHighPrecisionColorComponents= */ useHdr);
|
||||||
|
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, bitmapTexId);
|
||||||
|
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, bitmap, /* border= */ 0);
|
||||||
|
GlUtil.checkGlError();
|
||||||
|
} catch (GlUtil.GlException 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private void maybeQueueToShaderProgram() {
|
||||||
|
if (pendingBitmaps.isEmpty() || downstreamShaderProgramCapacity == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BitmapFrameSequenceInfo currentBitmap = checkNotNull(pendingBitmaps.peek());
|
||||||
|
if (framesToQueueForCurrentBitmap == 0) {
|
||||||
|
framesToQueueForCurrentBitmap = currentBitmap.numberOfFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
framesToQueueForCurrentBitmap--;
|
||||||
|
downstreamShaderProgramCapacity--;
|
||||||
|
|
||||||
|
shaderProgram.queueInputFrame(currentBitmap.textureInfo, currentPresentationTimeUs);
|
||||||
|
|
||||||
|
currentPresentationTimeUs += currentBitmap.frameDurationUs;
|
||||||
|
if (framesToQueueForCurrentBitmap == 0) {
|
||||||
|
pendingBitmaps.remove();
|
||||||
|
signalEndOfOutput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private void signalEndOfOutput() {
|
||||||
|
if (framesToQueueForCurrentBitmap == 0
|
||||||
|
&& pendingBitmaps.isEmpty()
|
||||||
|
&& inputEnded
|
||||||
|
&& !outputEnded) {
|
||||||
|
shaderProgram.signalEndOfCurrentInputStream();
|
||||||
|
outputEnded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value class specifying information to generate all the frames associated with a specific {@link
|
* Value class specifying information to generate all the frames associated with a specific {@link
|
||||||
* Bitmap}.
|
* Bitmap}.
|
||||||
*/
|
*/
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user