mirror of
https://github.com/androidx/media.git
synced 2025-05-10 09:12:16 +08:00
Add a timer to end a video stream prematurely in ExtTexMgr
PiperOrigin-RevId: 539036285
This commit is contained in:
parent
319854d624
commit
a66f08ba97
@ -205,7 +205,7 @@ public interface VideoFrameProcessor {
|
|||||||
* <p>Call {@link #setInputFrameInfo} before this method if the {@link FrameInfo} of the new input
|
* <p>Call {@link #setInputFrameInfo} before this method if the {@link FrameInfo} of the new input
|
||||||
* stream differs from that of the current input stream.
|
* stream differs from that of the current input stream.
|
||||||
*/
|
*/
|
||||||
// TODO(b/274109008) Merge this and setInputFrameInfo.
|
// TODO(b/286032822) Merge this and setInputFrameInfo.
|
||||||
void registerInputStream(@InputType int inputType);
|
void registerInputStream(@InputType int inputType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -219,6 +219,7 @@ public interface VideoFrameProcessor {
|
|||||||
*
|
*
|
||||||
* <p>Can be called on any thread.
|
* <p>Can be called on any thread.
|
||||||
*/
|
*/
|
||||||
|
// TODO(b/286032822) Simplify frame and stream registration.
|
||||||
void setInputFrameInfo(FrameInfo inputFrameInfo);
|
void setInputFrameInfo(FrameInfo inputFrameInfo);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,6 +17,7 @@ package androidx.media3.effect;
|
|||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
import android.graphics.SurfaceTexture;
|
import android.graphics.SurfaceTexture;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
@ -26,9 +27,13 @@ import androidx.media3.common.FrameInfo;
|
|||||||
import androidx.media3.common.GlTextureInfo;
|
import androidx.media3.common.GlTextureInfo;
|
||||||
import androidx.media3.common.VideoFrameProcessingException;
|
import androidx.media3.common.VideoFrameProcessingException;
|
||||||
import androidx.media3.common.util.GlUtil;
|
import androidx.media3.common.util.GlUtil;
|
||||||
|
import androidx.media3.common.util.Log;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.effect.GlShaderProgram.InputListener;
|
import androidx.media3.effect.GlShaderProgram.InputListener;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,6 +42,15 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
*/
|
*/
|
||||||
/* package */ final class ExternalTextureManager implements TextureManager {
|
/* package */ final class ExternalTextureManager implements TextureManager {
|
||||||
|
|
||||||
|
private static final String TAG = "ExtTexMgr";
|
||||||
|
private static final String TIMER_THREAD_NAME = "ExtTexMgr:Timer";
|
||||||
|
/**
|
||||||
|
* The time out in milliseconds after calling signalEndOfCurrentInputStream after which the input
|
||||||
|
* stream is considered to have ended, even if not all expected frames have been received from the
|
||||||
|
* decoder. This has been observed on some decoders.
|
||||||
|
*/
|
||||||
|
private static final long SURFACE_TEXTURE_TIMEOUT_MS = 500;
|
||||||
|
|
||||||
private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor;
|
private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor;
|
||||||
private final ExternalShaderProgram externalShaderProgram;
|
private final ExternalShaderProgram externalShaderProgram;
|
||||||
private final int externalTexId;
|
private final int externalTexId;
|
||||||
@ -44,6 +58,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
private final SurfaceTexture surfaceTexture;
|
private final SurfaceTexture surfaceTexture;
|
||||||
private final float[] textureTransformMatrix;
|
private final float[] textureTransformMatrix;
|
||||||
private final Queue<FrameInfo> pendingFrames;
|
private final Queue<FrameInfo> pendingFrames;
|
||||||
|
private final ScheduledExecutorService forceEndOfStreamExecutorService;
|
||||||
|
|
||||||
// Incremented on any thread, decremented on the GL thread only.
|
// Incremented on any thread, decremented on the GL thread only.
|
||||||
private final AtomicInteger externalShaderProgramInputCapacity;
|
private final AtomicInteger externalShaderProgramInputCapacity;
|
||||||
@ -66,6 +81,10 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
|
|
||||||
// TODO(b/238302341) Remove the use of after flush task, block the calling thread instead.
|
// TODO(b/238302341) Remove the use of after flush task, block the calling thread instead.
|
||||||
@Nullable private volatile VideoFrameProcessingTask onFlushCompleteTask;
|
@Nullable private volatile VideoFrameProcessingTask onFlushCompleteTask;
|
||||||
|
@Nullable private Future<?> forceSignalEndOfStreamFuture;
|
||||||
|
|
||||||
|
// Whether to reject frames from the SurfaceTexture. Accessed only on GL thread.
|
||||||
|
private boolean shouldRejectIncomingFrames;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance.
|
* Creates a new instance.
|
||||||
@ -91,6 +110,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
surfaceTexture = new SurfaceTexture(externalTexId);
|
surfaceTexture = new SurfaceTexture(externalTexId);
|
||||||
textureTransformMatrix = new float[16];
|
textureTransformMatrix = new float[16];
|
||||||
pendingFrames = new ConcurrentLinkedQueue<>();
|
pendingFrames = new ConcurrentLinkedQueue<>();
|
||||||
|
forceEndOfStreamExecutorService = Util.newSingleThreadScheduledExecutor(TIMER_THREAD_NAME);
|
||||||
externalShaderProgramInputCapacity = new AtomicInteger();
|
externalShaderProgramInputCapacity = new AtomicInteger();
|
||||||
surfaceTexture.setOnFrameAvailableListener(
|
surfaceTexture.setOnFrameAvailableListener(
|
||||||
unused ->
|
unused ->
|
||||||
@ -101,7 +121,16 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
numberOfFramesToDropOnBecomingAvailable--;
|
numberOfFramesToDropOnBecomingAvailable--;
|
||||||
surfaceTexture.updateTexImage();
|
surfaceTexture.updateTexImage();
|
||||||
maybeExecuteAfterFlushTask();
|
maybeExecuteAfterFlushTask();
|
||||||
|
} else if (shouldRejectIncomingFrames) {
|
||||||
|
surfaceTexture.updateTexImage();
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"Dropping frame received on SurfaceTexture after forcing EOS: "
|
||||||
|
+ surfaceTexture.getTimestamp() / 1000);
|
||||||
} else {
|
} else {
|
||||||
|
if (currentInputStreamEnded) {
|
||||||
|
restartForceSignalEndOfStreamTimer();
|
||||||
|
}
|
||||||
availableFrameCount++;
|
availableFrameCount++;
|
||||||
maybeQueueFrameToExternalShaderProgram();
|
maybeQueueFrameToExternalShaderProgram();
|
||||||
}
|
}
|
||||||
@ -138,6 +167,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
currentInputStreamEnded = false;
|
currentInputStreamEnded = false;
|
||||||
externalShaderProgram.signalEndOfCurrentInputStream();
|
externalShaderProgram.signalEndOfCurrentInputStream();
|
||||||
DebugTraceUtil.recordExternalInputManagerSignalEndOfCurrentInputStream();
|
DebugTraceUtil.recordExternalInputManagerSignalEndOfCurrentInputStream();
|
||||||
|
cancelForceSignalEndOfStreamTimer();
|
||||||
} else {
|
} else {
|
||||||
maybeQueueFrameToExternalShaderProgram();
|
maybeQueueFrameToExternalShaderProgram();
|
||||||
}
|
}
|
||||||
@ -165,6 +195,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
public void registerInputFrame(FrameInfo frame) {
|
public void registerInputFrame(FrameInfo frame) {
|
||||||
checkState(!inputStreamEnded);
|
checkState(!inputStreamEnded);
|
||||||
pendingFrames.add(frame);
|
pendingFrames.add(frame);
|
||||||
|
videoFrameProcessingTaskExecutor.submit(() -> shouldRejectIncomingFrames = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -185,8 +216,10 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
if (pendingFrames.isEmpty() && currentFrame == null) {
|
if (pendingFrames.isEmpty() && currentFrame == null) {
|
||||||
externalShaderProgram.signalEndOfCurrentInputStream();
|
externalShaderProgram.signalEndOfCurrentInputStream();
|
||||||
DebugTraceUtil.recordExternalInputManagerSignalEndOfCurrentInputStream();
|
DebugTraceUtil.recordExternalInputManagerSignalEndOfCurrentInputStream();
|
||||||
|
cancelForceSignalEndOfStreamTimer();
|
||||||
} else {
|
} else {
|
||||||
currentInputStreamEnded = true;
|
currentInputStreamEnded = true;
|
||||||
|
restartForceSignalEndOfStreamTimer();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -201,6 +234,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
public void release() {
|
public void release() {
|
||||||
surfaceTexture.release();
|
surfaceTexture.release();
|
||||||
surface.release();
|
surface.release();
|
||||||
|
forceEndOfStreamExecutorService.shutdownNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeExecuteAfterFlushTask() {
|
private void maybeExecuteAfterFlushTask() {
|
||||||
@ -212,6 +246,36 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
|
|
||||||
// Methods that must be called on the GL thread.
|
// Methods that must be called on the GL thread.
|
||||||
|
|
||||||
|
private void restartForceSignalEndOfStreamTimer() {
|
||||||
|
cancelForceSignalEndOfStreamTimer();
|
||||||
|
forceSignalEndOfStreamFuture =
|
||||||
|
forceEndOfStreamExecutorService.schedule(
|
||||||
|
() -> videoFrameProcessingTaskExecutor.submit(this::forceSignalEndOfStream),
|
||||||
|
SURFACE_TEXTURE_TIMEOUT_MS,
|
||||||
|
MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cancelForceSignalEndOfStreamTimer() {
|
||||||
|
if (forceSignalEndOfStreamFuture != null) {
|
||||||
|
forceSignalEndOfStreamFuture.cancel(/* mayInterruptIfRunning= */ false);
|
||||||
|
}
|
||||||
|
forceSignalEndOfStreamFuture = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void forceSignalEndOfStream() {
|
||||||
|
// Reset because there could be further input streams after the current one ends.
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
Util.formatInvariant(
|
||||||
|
"Forcing EOS after missing %d frames for %d ms",
|
||||||
|
pendingFrames.size(), SURFACE_TEXTURE_TIMEOUT_MS));
|
||||||
|
currentInputStreamEnded = false;
|
||||||
|
pendingFrames.clear();
|
||||||
|
currentFrame = null;
|
||||||
|
shouldRejectIncomingFrames = true;
|
||||||
|
signalEndOfCurrentInputStream();
|
||||||
|
}
|
||||||
|
|
||||||
private void flush() {
|
private void flush() {
|
||||||
// A frame that is registered before flush may arrive after flush.
|
// A frame that is registered before flush may arrive after flush.
|
||||||
numberOfFramesToDropOnBecomingAvailable = pendingFrames.size() - availableFrameCount;
|
numberOfFramesToDropOnBecomingAvailable = pendingFrames.size() - availableFrameCount;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user