Make repetitive decode/draw.
tl;dr: In the previous transformer, the transcoding flow is - If a the GL's input surface (from decoder) does not have data, wait 10ms (DO_SOME_WORK) - Else, make the decoder render **ONE** frame to the GL's input surface - Wait at least 10ms, until the frame's texture is available - Then process the texture The process is quite slow, so in the new version, we do: - If a the GL's input surface (from decoder) does not have data, wait 10ms (DO_SOME_WORK) **same** - Else, make the decoder render **as many frames** to the GL's input surface - Process **as many** available textures in this DO_SOME_WORK cycle PiperOrigin-RevId: 415474722
This commit is contained in:
parent
63935887b6
commit
1c9f99f939
@ -32,6 +32,7 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.util.GlUtil;
|
import androidx.media3.common.util.GlUtil;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
/** FrameEditor applies changes to individual video frames. */
|
/** FrameEditor applies changes to individual video frames. */
|
||||||
/* package */ final class FrameEditor {
|
/* package */ final class FrameEditor {
|
||||||
@ -178,6 +179,7 @@ import java.io.IOException;
|
|||||||
private final EGLContext eglContext;
|
private final EGLContext eglContext;
|
||||||
private final EGLSurface eglSurface;
|
private final EGLSurface eglSurface;
|
||||||
private final int textureId;
|
private final int textureId;
|
||||||
|
private final AtomicInteger pendingInputFrameCount;
|
||||||
private final SurfaceTexture inputSurfaceTexture;
|
private final SurfaceTexture inputSurfaceTexture;
|
||||||
private final Surface inputSurface;
|
private final Surface inputSurface;
|
||||||
private final GlUtil.Program glProgram;
|
private final GlUtil.Program glProgram;
|
||||||
@ -187,8 +189,6 @@ import java.io.IOException;
|
|||||||
private final int debugPreviewWidth;
|
private final int debugPreviewWidth;
|
||||||
private final int debugPreviewHeight;
|
private final int debugPreviewHeight;
|
||||||
|
|
||||||
private volatile boolean hasInputData;
|
|
||||||
|
|
||||||
private FrameEditor(
|
private FrameEditor(
|
||||||
EGLDisplay eglDisplay,
|
EGLDisplay eglDisplay,
|
||||||
EGLContext eglContext,
|
EGLContext eglContext,
|
||||||
@ -205,6 +205,7 @@ import java.io.IOException;
|
|||||||
this.eglSurface = eglSurface;
|
this.eglSurface = eglSurface;
|
||||||
this.textureId = textureId;
|
this.textureId = textureId;
|
||||||
this.glProgram = glProgram;
|
this.glProgram = glProgram;
|
||||||
|
this.pendingInputFrameCount = new AtomicInteger();
|
||||||
this.outputWidth = outputWidth;
|
this.outputWidth = outputWidth;
|
||||||
this.outputHeight = outputHeight;
|
this.outputHeight = outputHeight;
|
||||||
this.debugPreviewEglSurface = debugPreviewEglSurface;
|
this.debugPreviewEglSurface = debugPreviewEglSurface;
|
||||||
@ -212,7 +213,8 @@ import java.io.IOException;
|
|||||||
this.debugPreviewHeight = debugPreviewHeight;
|
this.debugPreviewHeight = debugPreviewHeight;
|
||||||
textureTransformMatrix = new float[16];
|
textureTransformMatrix = new float[16];
|
||||||
inputSurfaceTexture = new SurfaceTexture(textureId);
|
inputSurfaceTexture = new SurfaceTexture(textureId);
|
||||||
inputSurfaceTexture.setOnFrameAvailableListener(surfaceTexture -> hasInputData = true);
|
inputSurfaceTexture.setOnFrameAvailableListener(
|
||||||
|
surfaceTexture -> pendingInputFrameCount.incrementAndGet());
|
||||||
inputSurface = new Surface(inputSurfaceTexture);
|
inputSurface = new Surface(inputSurfaceTexture);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,7 +228,7 @@ import java.io.IOException;
|
|||||||
* #processData()}.
|
* #processData()}.
|
||||||
*/
|
*/
|
||||||
public boolean hasInputData() {
|
public boolean hasInputData() {
|
||||||
return hasInputData;
|
return pendingInputFrameCount.get() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Processes pending input frame. */
|
/** Processes pending input frame. */
|
||||||
@ -240,13 +242,12 @@ import java.io.IOException;
|
|||||||
long surfaceTextureTimestampNs = inputSurfaceTexture.getTimestamp();
|
long surfaceTextureTimestampNs = inputSurfaceTexture.getTimestamp();
|
||||||
EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, surfaceTextureTimestampNs);
|
EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, surfaceTextureTimestampNs);
|
||||||
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
|
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
|
||||||
|
pendingInputFrameCount.decrementAndGet();
|
||||||
|
|
||||||
if (debugPreviewEglSurface != null) {
|
if (debugPreviewEglSurface != null) {
|
||||||
focusAndDrawQuad(debugPreviewEglSurface, debugPreviewWidth, debugPreviewHeight);
|
focusAndDrawQuad(debugPreviewEglSurface, debugPreviewWidth, debugPreviewHeight);
|
||||||
EGL14.eglSwapBuffers(eglDisplay, debugPreviewEglSurface);
|
EGL14.eglSwapBuffers(eglDisplay, debugPreviewEglSurface);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasInputData = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Releases all resources. */
|
/** Releases all resources. */
|
||||||
|
@ -19,6 +19,7 @@ package androidx.media3.transformer;
|
|||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
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.Util.SDK_INT;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
@ -147,6 +148,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
MediaFormatUtil.maybeSetInteger(
|
MediaFormatUtil.maybeSetInteger(
|
||||||
mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
|
mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
|
||||||
MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData);
|
MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData);
|
||||||
|
|
||||||
|
if (SDK_INT >= 29) {
|
||||||
|
// On API levels over 29, Transformer decodes as many frames as possible in one render
|
||||||
|
// cycle. This key ensures no frame dropping when the decoder's output surface is full.
|
||||||
|
mediaFormat.setInteger(MediaFormat.KEY_ALLOW_FRAME_DROP, 0);
|
||||||
|
}
|
||||||
|
|
||||||
adapter =
|
adapter =
|
||||||
new Factory()
|
new Factory()
|
||||||
.createAdapter(
|
.createAdapter(
|
||||||
|
@ -17,10 +17,13 @@
|
|||||||
package androidx.media3.transformer;
|
package androidx.media3.transformer;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
|
import static androidx.media3.common.util.Util.SDK_INT;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
|
import android.media.MediaFormat;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.PlaybackException;
|
import androidx.media3.common.PlaybackException;
|
||||||
@ -145,6 +148,52 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SDK_INT >= 29) {
|
||||||
|
return processDataV29();
|
||||||
|
} else {
|
||||||
|
return processDataDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes input data from API 29.
|
||||||
|
*
|
||||||
|
* <p>In this method the decoder could decode multiple frames in one invocation; as compared to
|
||||||
|
* {@link #processDataDefault()}, in which one frame is decoded in each invocation. Consequently,
|
||||||
|
* if {@link FrameEditor} processes frames slower than the decoder, decoded frames are queued up
|
||||||
|
* in the decoder's output surface.
|
||||||
|
*
|
||||||
|
* <p>Prior to API 29, decoders may drop frames to keep their output surface from growing out of
|
||||||
|
* bound; while after API 29, the {@link MediaFormat#KEY_ALLOW_FRAME_DROP} key prevents frame
|
||||||
|
* dropping even when the surface is full. As dropping random frames is not acceptable in {@code
|
||||||
|
* Transformer}, using this method requires API level 29 or higher.
|
||||||
|
*/
|
||||||
|
@RequiresApi(29)
|
||||||
|
private boolean processDataV29() {
|
||||||
|
if (frameEditor != null) {
|
||||||
|
while (frameEditor.hasInputData()) {
|
||||||
|
// Processes as much frames in one invocation: FrameEditor's output surface will block
|
||||||
|
// FrameEditor when it's full. There will be no frame drop, or FrameEditor's output surface
|
||||||
|
// growing out of bound.
|
||||||
|
frameEditor.processData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (decoder.getOutputBufferInfo() != null) {
|
||||||
|
decoder.releaseOutputBuffer(/* render= */ true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decoder.isEnded()) {
|
||||||
|
// TODO(internal b/208986865): Handle possible last frame drop.
|
||||||
|
encoder.signalEndOfInputStream();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return frameEditor != null && frameEditor.hasInputData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Processes input data. */
|
||||||
|
private boolean processDataDefault() {
|
||||||
if (frameEditor != null) {
|
if (frameEditor != null) {
|
||||||
if (frameEditor.hasInputData()) {
|
if (frameEditor.hasInputData()) {
|
||||||
waitingForFrameEditorInput = false;
|
waitingForFrameEditorInput = false;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user