mirror of
https://github.com/samsonjs/media.git
synced 2026-04-04 11:05:47 +00:00
Converging BlankFrameProducer
`BlankFrameProducer` was originally internal to
`DefaultVideoFrameProcessorVideoFrameRenderingTest`. We later copied it in
c221958889. This CL converges the usages.
PiperOrigin-RevId: 555953620
This commit is contained in:
parent
b24f7678bb
commit
2980c30ace
1 changed files with 35 additions and 110 deletions
|
|
@ -25,25 +25,21 @@ import android.graphics.PixelFormat;
|
|||
import android.media.Image;
|
||||
import android.media.ImageReader;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.ColorInfo;
|
||||
import androidx.media3.common.DebugViewProvider;
|
||||
import androidx.media3.common.FrameInfo;
|
||||
import androidx.media3.common.GlObjectsProvider;
|
||||
import androidx.media3.common.GlTextureInfo;
|
||||
import androidx.media3.common.SurfaceInfo;
|
||||
import androidx.media3.common.VideoFrameProcessingException;
|
||||
import androidx.media3.common.VideoFrameProcessor;
|
||||
import androidx.media3.common.util.GlUtil;
|
||||
import androidx.media3.common.util.NullableType;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.primitives.Longs;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
|
@ -88,7 +84,7 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
|
|||
long originalPresentationTimeUs = 1234;
|
||||
AtomicLong actualPresentationTimeUs = new AtomicLong();
|
||||
processFramesToEndOfStream(
|
||||
/* inputPresentationTimesUs= */ new long[] {originalPresentationTimeUs},
|
||||
/* inputPresentationTimesUs= */ ImmutableList.of(originalPresentationTimeUs),
|
||||
/* onFrameAvailableListener= */ actualPresentationTimeUs::set,
|
||||
/* renderFramesAutomatically= */ true);
|
||||
|
||||
|
|
@ -100,7 +96,7 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
|
|||
|
||||
@Test
|
||||
public void automaticFrameRendering_withThreeFrames_reusesInputTimestamps() throws Exception {
|
||||
long[] originalPresentationTimesUs = new long[] {1234, 3456, 4567};
|
||||
ImmutableList<Long> originalPresentationTimesUs = ImmutableList.of(1234L, 3456L, 4567L);
|
||||
ArrayList<Long> actualPresentationTimesUs = new ArrayList<>();
|
||||
processFramesToEndOfStream(
|
||||
originalPresentationTimesUs,
|
||||
|
|
@ -119,18 +115,14 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
|
|||
/* renderFramesAutomatically= */ true);
|
||||
|
||||
assertThat(actualPresentationTimesUs)
|
||||
.containsExactly(
|
||||
originalPresentationTimesUs[0],
|
||||
originalPresentationTimesUs[1],
|
||||
originalPresentationTimesUs[2])
|
||||
.containsExactlyElementsIn(originalPresentationTimesUs)
|
||||
.inOrder();
|
||||
ImmutableList<Long> actualRenderTimesNs =
|
||||
waitForFrameRenderingAndGetRenderTimesNs(/* expectedFrameCount= */ 3);
|
||||
assertThat(actualRenderTimesNs)
|
||||
.containsExactly(
|
||||
MICROS_TO_NANOS * originalPresentationTimesUs[0],
|
||||
MICROS_TO_NANOS * originalPresentationTimesUs[1],
|
||||
MICROS_TO_NANOS * originalPresentationTimesUs[2])
|
||||
.containsExactlyElementsIn(
|
||||
// Convert microseconds to nanoseconds.
|
||||
Iterables.transform(originalPresentationTimesUs, input -> MICROS_TO_NANOS * input))
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +132,7 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
|
|||
long renderTimesNs = System.nanoTime() + 345678;
|
||||
AtomicLong actualPresentationTimeUs = new AtomicLong();
|
||||
processFramesToEndOfStream(
|
||||
/* inputPresentationTimesUs= */ new long[] {originalPresentationTimeUs},
|
||||
/* inputPresentationTimesUs= */ ImmutableList.of(originalPresentationTimeUs),
|
||||
/* onFrameAvailableListener= */ presentationTimeUs -> {
|
||||
actualPresentationTimeUs.set(presentationTimeUs);
|
||||
checkNotNull(defaultVideoFrameProcessor).renderOutputFrame(renderTimesNs);
|
||||
|
|
@ -159,7 +151,7 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
|
|||
long renderTimesNs = VideoFrameProcessor.RENDER_OUTPUT_FRAME_IMMEDIATELY;
|
||||
AtomicLong actualPresentationTimeUs = new AtomicLong();
|
||||
processFramesToEndOfStream(
|
||||
/* inputPresentationTimesUs= */ new long[] {originalPresentationTimeUs},
|
||||
/* inputPresentationTimesUs= */ ImmutableList.of(originalPresentationTimeUs),
|
||||
/* onFrameAvailableListener= */ presentationTimeUs -> {
|
||||
actualPresentationTimeUs.set(presentationTimeUs);
|
||||
checkNotNull(defaultVideoFrameProcessor).renderOutputFrame(renderTimesNs);
|
||||
|
|
@ -179,7 +171,7 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
|
|||
long renderTimeBeforeCurrentTimeNs = System.nanoTime() - 345678;
|
||||
AtomicLong actualPresentationTimeUs = new AtomicLong();
|
||||
processFramesToEndOfStream(
|
||||
/* inputPresentationTimesUs= */ new long[] {originalPresentationTimeUs},
|
||||
/* inputPresentationTimesUs= */ ImmutableList.of(originalPresentationTimeUs),
|
||||
/* onFrameAvailableListener= */ presentationTimeUs -> {
|
||||
actualPresentationTimeUs.set(presentationTimeUs);
|
||||
checkNotNull(defaultVideoFrameProcessor).renderOutputFrame(renderTimeBeforeCurrentTimeNs);
|
||||
|
|
@ -198,7 +190,7 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
|
|||
long originalPresentationTimeUs = 1234;
|
||||
AtomicLong actualPresentationTimeUs = new AtomicLong();
|
||||
processFramesToEndOfStream(
|
||||
/* inputPresentationTimesUs= */ new long[] {originalPresentationTimeUs},
|
||||
/* inputPresentationTimesUs= */ ImmutableList.of(originalPresentationTimeUs),
|
||||
/* onFrameAvailableListener= */ presentationTimeNs -> {
|
||||
actualPresentationTimeUs.set(presentationTimeNs);
|
||||
checkNotNull(defaultVideoFrameProcessor)
|
||||
|
|
@ -212,9 +204,10 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
|
|||
@Test
|
||||
public void controlledFrameRendering_withThreeIndividualFrames_usesGivenTimestamps()
|
||||
throws Exception {
|
||||
long[] originalPresentationTimesUs = new long[] {1234, 3456, 4567};
|
||||
ImmutableList<Long> originalPresentationTimesUs = ImmutableList.of(1234L, 3456L, 4567L);
|
||||
long offsetNs = System.nanoTime();
|
||||
long[] renderTimesNs = new long[] {offsetNs + 123456, offsetNs + 234567, offsetNs + 345678};
|
||||
ImmutableList<Long> renderTimesNs =
|
||||
ImmutableList.of(offsetNs + 123456, offsetNs + 234567, offsetNs + 345678);
|
||||
ArrayList<Long> actualPresentationTimesUs = new ArrayList<>();
|
||||
AtomicInteger frameIndex = new AtomicInteger();
|
||||
processFramesToEndOfStream(
|
||||
|
|
@ -222,7 +215,7 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
|
|||
/* onFrameAvailableListener= */ presentationTimeUs -> {
|
||||
actualPresentationTimesUs.add(presentationTimeUs);
|
||||
checkNotNull(defaultVideoFrameProcessor)
|
||||
.renderOutputFrame(renderTimesNs[frameIndex.getAndIncrement()]);
|
||||
.renderOutputFrame(renderTimesNs.get(frameIndex.getAndIncrement()));
|
||||
try {
|
||||
// TODO(b/264252759): Investigate output frames being dropped and remove sleep.
|
||||
// Frames can be dropped silently between EGL and the ImageReader. Sleep after each call
|
||||
|
|
@ -236,24 +229,22 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
|
|||
/* renderFramesAutomatically= */ false);
|
||||
|
||||
assertThat(actualPresentationTimesUs)
|
||||
.containsExactly(
|
||||
originalPresentationTimesUs[0],
|
||||
originalPresentationTimesUs[1],
|
||||
originalPresentationTimesUs[2])
|
||||
.containsExactlyElementsIn(originalPresentationTimesUs)
|
||||
.inOrder();
|
||||
int actualFrameCount = frameIndex.get();
|
||||
assertThat(actualFrameCount).isEqualTo(originalPresentationTimesUs.length);
|
||||
long[] actualRenderTimesNs =
|
||||
Longs.toArray(waitForFrameRenderingAndGetRenderTimesNs(actualFrameCount));
|
||||
assertThat(actualRenderTimesNs).isEqualTo(renderTimesNs);
|
||||
assertThat(actualFrameCount).isEqualTo(originalPresentationTimesUs.size());
|
||||
ImmutableList<Long> actualRenderTimesNs =
|
||||
waitForFrameRenderingAndGetRenderTimesNs(actualFrameCount);
|
||||
assertThat(actualRenderTimesNs).containsExactlyElementsIn(renderTimesNs).inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void controlledFrameRendering_withThreeFramesAtOnce_usesGivenTimestamps()
|
||||
throws Exception {
|
||||
long[] originalPresentationTimesUs = new long[] {1234, 3456, 4567};
|
||||
ImmutableList<Long> originalPresentationTimesUs = ImmutableList.of(1234L, 3456L, 4567L);
|
||||
long offsetNs = System.nanoTime();
|
||||
long[] renderTimesNs = new long[] {offsetNs + 123456, offsetNs + 234567, offsetNs + 345678};
|
||||
ImmutableList<Long> renderTimesNs =
|
||||
ImmutableList.of(offsetNs + 123456, offsetNs + 234567, offsetNs + 345678);
|
||||
ArrayList<Long> actualPresentationTimesUs = new ArrayList<>();
|
||||
processFramesToEndOfStream(
|
||||
/* inputPresentationTimesUs= */ originalPresentationTimesUs,
|
||||
|
|
@ -263,22 +254,19 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
|
|||
// TODO(b/264252759): Investigate output frames being dropped and remove sleep.
|
||||
// Frames can be dropped silently between EGL and the ImageReader. Sleep after each call
|
||||
// to swap buffers, to avoid this behavior.
|
||||
defaultVideoFrameProcessor.renderOutputFrame(renderTimesNs[0]);
|
||||
defaultVideoFrameProcessor.renderOutputFrame(renderTimesNs.get(0));
|
||||
Thread.sleep(PER_FRAME_RENDERING_WAIT_TIME_MS);
|
||||
defaultVideoFrameProcessor.renderOutputFrame(renderTimesNs[1]);
|
||||
defaultVideoFrameProcessor.renderOutputFrame(renderTimesNs.get(1));
|
||||
Thread.sleep(PER_FRAME_RENDERING_WAIT_TIME_MS);
|
||||
defaultVideoFrameProcessor.renderOutputFrame(renderTimesNs[2]);
|
||||
defaultVideoFrameProcessor.renderOutputFrame(renderTimesNs.get(2));
|
||||
Thread.sleep(PER_FRAME_RENDERING_WAIT_TIME_MS);
|
||||
|
||||
assertThat(actualPresentationTimesUs)
|
||||
.containsExactly(
|
||||
originalPresentationTimesUs[0],
|
||||
originalPresentationTimesUs[1],
|
||||
originalPresentationTimesUs[2])
|
||||
.containsExactlyElementsIn(originalPresentationTimesUs)
|
||||
.inOrder();
|
||||
long[] actualRenderTimesNs =
|
||||
Longs.toArray(waitForFrameRenderingAndGetRenderTimesNs(/* expectedFrameCount= */ 3));
|
||||
assertThat(actualRenderTimesNs).isEqualTo(renderTimesNs);
|
||||
ImmutableList<Long> actualRenderTimesNs =
|
||||
waitForFrameRenderingAndGetRenderTimesNs(/* expectedFrameCount= */ 3);
|
||||
assertThat(actualRenderTimesNs).containsExactlyElementsIn(renderTimesNs).inOrder();
|
||||
}
|
||||
|
||||
private interface OnOutputFrameAvailableForRenderingListener {
|
||||
|
|
@ -287,13 +275,13 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
|
|||
|
||||
@EnsuresNonNull("defaultVideoFrameProcessor")
|
||||
private void processFramesToEndOfStream(
|
||||
long[] inputPresentationTimesUs,
|
||||
List<Long> inputPresentationTimesUs,
|
||||
OnOutputFrameAvailableForRenderingListener onFrameAvailableListener,
|
||||
boolean renderFramesAutomatically)
|
||||
throws Exception {
|
||||
AtomicReference<@NullableType VideoFrameProcessingException>
|
||||
videoFrameProcessingExceptionReference = new AtomicReference<>();
|
||||
BlankFrameProducer blankFrameProducer = new BlankFrameProducer();
|
||||
BlankFrameProducer blankFrameProducer = new BlankFrameProducer(WIDTH, HEIGHT);
|
||||
CountDownLatch videoFrameProcessingEndedCountDownLatch = new CountDownLatch(1);
|
||||
defaultVideoFrameProcessor =
|
||||
checkNotNull(
|
||||
|
|
@ -314,7 +302,7 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
|
|||
width,
|
||||
height,
|
||||
PixelFormat.RGBA_8888,
|
||||
/* maxImages= */ inputPresentationTimesUs.length);
|
||||
/* maxImages= */ inputPresentationTimesUs.size());
|
||||
checkNotNull(defaultVideoFrameProcessor)
|
||||
.setOutputSurfaceInfo(
|
||||
new SurfaceInfo(outputImageReader.getSurface(), width, height));
|
||||
|
|
@ -352,7 +340,7 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
|
|||
INPUT_TYPE_SURFACE,
|
||||
/* effects= */ ImmutableList.of((GlEffect) (context, useHdr) -> blankFrameProducer),
|
||||
new FrameInfo.Builder(WIDTH, HEIGHT).build());
|
||||
blankFrameProducer.produceBlankFramesAndQueueEndOfStream(inputPresentationTimesUs);
|
||||
blankFrameProducer.produceBlankFrames(inputPresentationTimesUs);
|
||||
defaultVideoFrameProcessor.signalEndOfInput();
|
||||
videoFrameProcessingEndedCountDownLatch.await();
|
||||
@Nullable
|
||||
|
|
@ -373,67 +361,4 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
|
|||
assertThat(outputRenderTimesNs).isEmpty();
|
||||
return listBuilder.build();
|
||||
}
|
||||
|
||||
/** Produces blank frames with the given timestamps. */
|
||||
private static final class BlankFrameProducer implements GlShaderProgram {
|
||||
|
||||
private @MonotonicNonNull GlTextureInfo blankTexture;
|
||||
private @MonotonicNonNull OutputListener outputListener;
|
||||
|
||||
public void configureGlObjects() throws VideoFrameProcessingException {
|
||||
try {
|
||||
int texId =
|
||||
GlUtil.createTexture(WIDTH, HEIGHT, /* useHighPrecisionColorComponents= */ false);
|
||||
int fboId = GlUtil.createFboForTexture(texId);
|
||||
blankTexture = new GlTextureInfo(texId, fboId, /* rboId= */ C.INDEX_UNSET, WIDTH, HEIGHT);
|
||||
GlUtil.focusFramebufferUsingCurrentContext(fboId, WIDTH, HEIGHT);
|
||||
GlUtil.clearFocusedBuffers();
|
||||
} catch (GlUtil.GlException e) {
|
||||
throw new VideoFrameProcessingException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void produceBlankFramesAndQueueEndOfStream(long[] presentationTimesUs) {
|
||||
checkNotNull(outputListener);
|
||||
for (long presentationTimeUs : presentationTimesUs) {
|
||||
outputListener.onOutputFrameAvailable(checkNotNull(blankTexture), presentationTimeUs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputListener(InputListener inputListener) {}
|
||||
|
||||
@Override
|
||||
public void setOutputListener(OutputListener outputListener) {
|
||||
this.outputListener = outputListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setErrorListener(Executor executor, ErrorListener errorListener) {}
|
||||
|
||||
@Override
|
||||
public void queueInputFrame(
|
||||
GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs) {
|
||||
// No input is queued in these tests. The BlankFrameProducer is used to produce frames.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseOutputFrame(GlTextureInfo outputTexture) {}
|
||||
|
||||
@Override
|
||||
public void signalEndOfCurrentInputStream() {
|
||||
checkNotNull(outputListener).onCurrentOutputStreamEnded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
// Do nothing as destroying the OpenGL context destroys the texture.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue