Remove late frame dropping in FrameProcessor

Currently, a frame is dropped if it's requested release time is in the past.
This mode was added to support previewing. However, in normal ExoPlayer
playback, slightly late frames (<30ms late) are also rendered. On MediaCodec
side, this means calling `releaseOutputBuffer` with a release time in the
past.

PiperOrigin-RevId: 479615291
This commit is contained in:
claincly 2022-10-07 17:29:15 +00:00 committed by Marc Baechinger
parent 36e12fbadb
commit a426cb27c0
3 changed files with 25 additions and 34 deletions

View File

@ -16,6 +16,7 @@
package androidx.media3.common; package androidx.media3.common;
import android.content.Context; import android.content.Context;
import android.opengl.EGLExt;
import android.view.Surface; import android.view.Surface;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
@ -174,10 +175,12 @@ public interface FrameProcessor {
* false} using the {@link Factory} and should be called exactly once for each frame that becomes * false} using the {@link Factory} and should be called exactly once for each frame that becomes
* {@linkplain Listener#onOutputFrameAvailable(long) available}. * {@linkplain Listener#onOutputFrameAvailable(long) available}.
* *
* @param releaseTimeNs The release time to use for the frame, in nanoseconds. Use {@link * <p>The {@code releaseTimeNs} may be passed to {@link EGLExt#eglPresentationTimeANDROID}
* #DROP_OUTPUT_FRAME} to drop the frame, or {@link #RELEASE_OUTPUT_FRAME_IMMEDIATELY} to * depending on the implementation.
* release the frame immediately. If {@code releaseTimeNs} is after {@link System#nanoTime()} *
* at the time of the release, the frame is also dropped. * @param releaseTimeNs The release time to use for the frame, in nanoseconds. The release time
* can be before of after the current system time. Use {@link #DROP_OUTPUT_FRAME} to drop the
* frame, or {@link #RELEASE_OUTPUT_FRAME_IMMEDIATELY} to release the frame immediately.
*/ */
void releaseOutputFrame(long releaseTimeNs); void releaseOutputFrame(long releaseTimeNs);

View File

@ -158,7 +158,7 @@ public final class GlEffectsFrameProcessorFrameReleaseTest {
} }
@Test @Test
public void controlledFrameRelease_withLateFrame_dropsFrame() throws Exception { public void controlledFrameRelease_withLateFrame_releasesFrame() throws Exception {
long originalPresentationTimeUs = 1234; long originalPresentationTimeUs = 1234;
long releaseTimeBeforeCurrentTimeNs = System.nanoTime() - 345678; long releaseTimeBeforeCurrentTimeNs = System.nanoTime() - 345678;
AtomicLong actualPresentationTimeUs = new AtomicLong(); AtomicLong actualPresentationTimeUs = new AtomicLong();
@ -175,7 +175,9 @@ public final class GlEffectsFrameProcessorFrameReleaseTest {
assertThat(frameProcessingException.get()).isNull(); assertThat(frameProcessingException.get()).isNull();
assertThat(actualPresentationTimeUs.get()).isEqualTo(originalPresentationTimeUs); assertThat(actualPresentationTimeUs.get()).isEqualTo(originalPresentationTimeUs);
assertThat(outputReleaseTimesNs).isEmpty(); assertThat(outputReleaseTimesNs).hasSize(1);
// The actual release time is determined by the FrameProcessor when releasing the frame.
assertThat(outputReleaseTimesNs.remove()).isAtLeast(releaseTimeBeforeCurrentTimeNs);
} }
@Test @Test

View File

@ -150,10 +150,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
frameProcessorListener.onOutputFrameAvailable(offsetPresentationTimeUs); frameProcessorListener.onOutputFrameAvailable(offsetPresentationTimeUs);
if (releaseFramesAutomatically) { if (releaseFramesAutomatically) {
renderFrameToSurfaces( renderFrameToSurfaces(
inputTexture, inputTexture, presentationTimeUs, /* releaseTimeNs= */ offsetPresentationTimeUs * 1000);
presentationTimeUs,
/* releaseTimeNs= */ offsetPresentationTimeUs * 1000,
/* dropLateFrame= */ false);
} else { } else {
availableFrames.add(Pair.create(inputTexture, presentationTimeUs)); availableFrames.add(Pair.create(inputTexture, presentationTimeUs));
} }
@ -169,21 +166,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@WorkerThread @WorkerThread
public void releaseOutputFrame(long releaseTimeNs) { public void releaseOutputFrame(long releaseTimeNs) {
checkState(!releaseFramesAutomatically); checkState(!releaseFramesAutomatically);
boolean dropLateFrame = true;
if (releaseTimeNs == FrameProcessor.RELEASE_OUTPUT_FRAME_IMMEDIATELY) {
dropLateFrame = false;
releaseTimeNs = System.nanoTime();
} else if (releaseTimeNs == FrameProcessor.DROP_OUTPUT_FRAME) {
releaseTimeNs = C.TIME_UNSET;
}
Pair<TextureInfo, Long> oldestAvailableFrame = availableFrames.remove(); Pair<TextureInfo, Long> oldestAvailableFrame = availableFrames.remove();
renderFrameToSurfaces( renderFrameToSurfaces(
/* inputTexture= */ oldestAvailableFrame.first, /* inputTexture= */ oldestAvailableFrame.first,
/* presentationTimeUs= */ oldestAvailableFrame.second, /* presentationTimeUs= */ oldestAvailableFrame.second,
releaseTimeNs, releaseTimeNs);
dropLateFrame);
} }
@Override @Override
@ -254,13 +241,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
private void renderFrameToSurfaces( private void renderFrameToSurfaces(
TextureInfo inputTexture, TextureInfo inputTexture, long presentationTimeUs, long releaseTimeNs) {
long presentationTimeUs,
long releaseTimeNs,
boolean dropLateFrame) {
try { try {
maybeRenderFrameToOutputSurface( maybeRenderFrameToOutputSurface(inputTexture, presentationTimeUs, releaseTimeNs);
inputTexture, presentationTimeUs, releaseTimeNs, dropLateFrame);
} catch (FrameProcessingException | GlUtil.GlException e) { } catch (FrameProcessingException | GlUtil.GlException e) {
frameProcessorListener.onFrameProcessingError( frameProcessorListener.onFrameProcessingError(
FrameProcessingException.from(e, presentationTimeUs)); FrameProcessingException.from(e, presentationTimeUs));
@ -270,10 +253,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
private synchronized void maybeRenderFrameToOutputSurface( private synchronized void maybeRenderFrameToOutputSurface(
TextureInfo inputTexture, long presentationTimeUs, long releaseTimeNs, boolean dropLateFrame) TextureInfo inputTexture, long presentationTimeUs, long releaseTimeNs)
throws FrameProcessingException, GlUtil.GlException { throws FrameProcessingException, GlUtil.GlException {
if (!ensureConfigured(inputTexture.width, inputTexture.height)) { if (releaseTimeNs == FrameProcessor.DROP_OUTPUT_FRAME
return; // Drop frames when there is no output surface. || !ensureConfigured(inputTexture.width, inputTexture.height)) {
return; // Drop frames when requested, or there is no output surface.
} }
EGLSurface outputEglSurface = this.outputEglSurface; EGLSurface outputEglSurface = this.outputEglSurface;
@ -289,10 +273,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
GlUtil.clearOutputFrame(); GlUtil.clearOutputFrame();
matrixTextureProcessor.drawFrame(inputTexture.texId, presentationTimeUs); matrixTextureProcessor.drawFrame(inputTexture.texId, presentationTimeUs);
if (dropLateFrame && System.nanoTime() > releaseTimeNs) { EGLExt.eglPresentationTimeANDROID(
return; eglDisplay,
} outputEglSurface,
EGLExt.eglPresentationTimeANDROID(eglDisplay, outputEglSurface, releaseTimeNs); releaseTimeNs == FrameProcessor.RELEASE_OUTPUT_FRAME_IMMEDIATELY
? System.nanoTime()
: releaseTimeNs);
EGL14.eglSwapBuffers(eglDisplay, outputEglSurface); EGL14.eglSwapBuffers(eglDisplay, outputEglSurface);
} }