diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/FrameProcessingTask.java b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameProcessingTask.java new file mode 100644 index 0000000000..e5e12dc14c --- /dev/null +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameProcessingTask.java @@ -0,0 +1,27 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.transformer; + +import androidx.media3.common.util.GlUtil; + +/** + * Interface for tasks that may throw a {@link GlUtil.GlException} or {@link + * FrameProcessingException}. + */ +/* package */ interface FrameProcessingTask { + /** Runs the task. */ + void run() throws FrameProcessingException, GlUtil.GlException; +} diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/FrameProcessingTaskExecutor.java b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameProcessingTaskExecutor.java new file mode 100644 index 0000000000..d228494831 --- /dev/null +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameProcessingTaskExecutor.java @@ -0,0 +1,123 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.transformer; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import androidx.media3.common.util.GlUtil; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Wrapper around a single thread {@link ExecutorService} for executing {@link FrameProcessingTask} + * instances. + * + *
The wrapper handles calling {@link
+ * FrameProcessorChain.Listener#onFrameProcessingError(FrameProcessingException)} for errors that
+ * occur during these tasks.
+ */
+/* package */ final class FrameProcessingTaskExecutor {
+
+ private final ExecutorService singleThreadExecutorService;
+ private final FrameProcessorChain.Listener listener;
+ private final ConcurrentLinkedQueue This method must be called on the {@linkplain #THREAD_NAME background thread}.
*/
@WorkerThread
- private void processFrame() {
- if (stopProcessing.get()) {
- return;
- }
+ private void processFrame() throws FrameProcessingException {
+ checkState(Thread.currentThread().getName().equals(THREAD_NAME));
+
+ inputSurfaceTexture.updateTexImage();
+ long inputFrameTimeNs = inputSurfaceTexture.getTimestamp();
+ // Correct for the stream offset so processors see original media presentation timestamps.
+ long presentationTimeUs = inputFrameTimeNs / 1000 - streamOffsetUs;
+ inputSurfaceTexture.getTransformMatrix(textureTransformMatrix);
+ ((ExternalTextureProcessor) textureProcessors.get(0))
+ .setTextureTransformMatrix(textureTransformMatrix);
+ int inputTexId = inputExternalTexId;
- long presentationTimeUs = C.TIME_UNSET;
try {
- checkState(Thread.currentThread().getName().equals(THREAD_NAME));
-
- inputSurfaceTexture.updateTexImage();
- long inputFrameTimeNs = inputSurfaceTexture.getTimestamp();
- // Correct for the stream offset so processors see original media presentation timestamps.
- presentationTimeUs = inputFrameTimeNs / 1000 - streamOffsetUs;
- inputSurfaceTexture.getTransformMatrix(textureTransformMatrix);
- ((ExternalTextureProcessor) textureProcessors.get(0))
- .setTextureTransformMatrix(textureTransformMatrix);
- int inputTexId = inputExternalTexId;
-
for (int i = 0; i < textureProcessors.size() - 1; i++) {
if (stopProcessing.get()) {
return;
@@ -550,27 +519,25 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
EGLExt.eglPresentationTimeANDROID(eglDisplay, outputEglSurface, inputFrameTimeNs);
EGL14.eglSwapBuffers(eglDisplay, outputEglSurface);
+ } catch (GlUtil.GlException e) {
+ throw new FrameProcessingException(e, presentationTimeUs);
+ }
+ try {
if (debugSurfaceViewWrapper != null) {
long finalPresentationTimeUs = presentationTimeUs;
int finalInputTexId = inputTexId;
debugSurfaceViewWrapper.maybeRenderToSurfaceView(
() -> {
- try {
- GlUtil.clearOutputFrame();
- getLast(textureProcessors).drawFrame(finalInputTexId, finalPresentationTimeUs);
- } catch (GlUtil.GlException | FrameProcessingException e) {
- Log.d(TAG, "Error rendering to debug preview", e);
- }
+ GlUtil.clearOutputFrame();
+ getLast(textureProcessors).drawFrame(finalInputTexId, finalPresentationTimeUs);
});
}
-
- checkState(pendingFrameCount.getAndDecrement() > 0);
- } catch (FrameProcessingException | GlUtil.GlException | RuntimeException e) {
- if (!stopProcessing.getAndSet(true)) {
- listener.onFrameProcessingError(FrameProcessingException.from(e, presentationTimeUs));
- }
+ } catch (FrameProcessingException | GlUtil.GlException e) {
+ Log.d(TAG, "Error rendering to debug preview", e);
}
+
+ checkState(pendingFrameCount.getAndDecrement() > 0);
}
/** Calls {@link Listener#onFrameProcessingEnded()} once no more frames are pending. */
@@ -579,7 +546,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
if (getPendingFrameCount() == 0) {
listener.onFrameProcessingEnded();
} else {
- futures.add(singleThreadExecutorService.submit(this::signalEndOfOutputStream));
+ frameProcessingTaskExecutor.submit(this::signalEndOfOutputStream);
}
}
@@ -590,15 +557,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* This method must be called on the {@linkplain #THREAD_NAME background thread}.
*/
@WorkerThread
- private void releaseTextureProcessorsAndDestroyGlContext() {
- try {
- for (int i = 0; i < textureProcessors.size(); i++) {
- textureProcessors.get(i).release();
- }
- GlUtil.destroyEglContext(eglDisplay, eglContext);
- } catch (FrameProcessingException | GlUtil.GlException | RuntimeException e) {
- listener.onFrameProcessingError(FrameProcessingException.from(e));
+ private void releaseTextureProcessorsAndDestroyGlContext()
+ throws GlUtil.GlException, FrameProcessingException {
+ for (int i = 0; i < textureProcessors.size(); i++) {
+ textureProcessors.get(i).release();
}
+ GlUtil.destroyEglContext(eglDisplay, eglContext);
}
/**
@@ -627,12 +591,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Focuses the wrapped surface view's surface as an {@link EGLSurface}, renders using {@code
- * renderRunnable} and swaps buffers, if the view's holder has a valid surface. Does nothing
+ * renderingTask} and swaps buffers, if the view's holder has a valid surface. Does nothing
* otherwise.
*/
@WorkerThread
- public synchronized void maybeRenderToSurfaceView(Runnable renderRunnable)
- throws GlUtil.GlException {
+ public synchronized void maybeRenderToSurfaceView(FrameProcessingTask renderingTask)
+ throws GlUtil.GlException, FrameProcessingException {
if (surface == null) {
return;
}
@@ -646,7 +610,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
EGLSurface eglSurface = this.eglSurface;
GlUtil.focusEglSurface(eglDisplay, eglContext, eglSurface, width, height);
- renderRunnable.run();
+ renderingTask.run();
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
}