mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Allow FrameProcessor input surface size changes.
This will be useful for downgrading to a lower resolution during a slow preview and for processing slide-shows once sequential multi-asset editing is supported. PiperOrigin-RevId: 457017255
This commit is contained in:
parent
638c758721
commit
30c52c58f0
@ -367,8 +367,6 @@ public final class GlEffectsFrameProcessorPixelTest {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
pixelWidthHeightRatio,
|
pixelWidthHeightRatio,
|
||||||
inputWidth,
|
|
||||||
inputHeight,
|
|
||||||
/* streamOffsetUs= */ 0L,
|
/* streamOffsetUs= */ 0L,
|
||||||
effects,
|
effects,
|
||||||
/* outputSurfaceProvider= */ (requestedWidth, requestedHeight) -> {
|
/* outputSurfaceProvider= */ (requestedWidth, requestedHeight) -> {
|
||||||
@ -383,6 +381,7 @@ public final class GlEffectsFrameProcessorPixelTest {
|
|||||||
},
|
},
|
||||||
Transformer.DebugViewProvider.NONE,
|
Transformer.DebugViewProvider.NONE,
|
||||||
/* enableExperimentalHdrEditing= */ false));
|
/* enableExperimentalHdrEditing= */ false));
|
||||||
|
glEffectsFrameProcessor.setInputFrameInfo(new FrameInfo(inputWidth, inputHeight));
|
||||||
glEffectsFrameProcessor.registerInputFrame();
|
glEffectsFrameProcessor.registerInputFrame();
|
||||||
|
|
||||||
// Queue the first video frame from the extractor.
|
// Queue the first video frame from the extractor.
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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 androidx.media3.common.util.Assertions.checkArgument;
|
||||||
|
|
||||||
|
/** Value class specifying information about a decoded video frame. */
|
||||||
|
/* package */ class FrameInfo {
|
||||||
|
/** The width of the frame, in pixels. */
|
||||||
|
public final int width;
|
||||||
|
/** The height of the frame, in pixels. */
|
||||||
|
public final int height;
|
||||||
|
|
||||||
|
// TODO(b/227625423): Add pixelWidthHeightRatio.
|
||||||
|
// TODO(b/227624622): Add color space information for HDR.
|
||||||
|
|
||||||
|
public FrameInfo(int width, int height) {
|
||||||
|
checkArgument(width > 0, "width must be positive, but is: " + width);
|
||||||
|
checkArgument(height > 0, "height must be positive, but is: " + height);
|
||||||
|
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
}
|
@ -41,12 +41,21 @@ import android.view.Surface;
|
|||||||
/** Returns the input {@link Surface}. */
|
/** Returns the input {@link Surface}. */
|
||||||
Surface getInputSurface();
|
Surface getInputSurface();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets information about the input frames.
|
||||||
|
*
|
||||||
|
* <p>The new input information is applied from the next frame {@linkplain #registerInputFrame()
|
||||||
|
* registered} onwards.
|
||||||
|
*/
|
||||||
|
void setInputFrameInfo(FrameInfo inputFrameInfo);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Informs the {@code FrameProcessor} that a frame will be queued to its input surface.
|
* Informs the {@code FrameProcessor} that a frame will be queued to its input surface.
|
||||||
*
|
*
|
||||||
* <p>Must be called before rendering a frame to the frame processor's input surface.
|
* <p>Must be called before rendering a frame to the frame processor's input surface.
|
||||||
*
|
*
|
||||||
* @throws IllegalStateException If called after {@link #signalEndOfInputStream()}.
|
* @throws IllegalStateException If called after {@link #signalEndOfInputStream()} or before
|
||||||
|
* {@link #setInputFrameInfo(FrameInfo)}.
|
||||||
*/
|
*/
|
||||||
void registerInputFrame();
|
void registerInputFrame();
|
||||||
|
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.transformer;
|
package androidx.media3.transformer;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
|
||||||
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 android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.SurfaceTexture;
|
import android.graphics.SurfaceTexture;
|
||||||
@ -31,10 +31,11 @@ import androidx.media3.common.util.GlUtil;
|
|||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link FrameProcessor} implementation that applies {@link GlEffect} instances using OpenGL on a
|
* A {@link FrameProcessor} implementation that applies {@link GlEffect} instances using OpenGL on a
|
||||||
@ -51,8 +52,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
* @param listener A {@link Listener}.
|
* @param listener A {@link Listener}.
|
||||||
* @param pixelWidthHeightRatio The ratio of width over height for each pixel. Pixels are expanded
|
* @param pixelWidthHeightRatio The ratio of width over height for each pixel. Pixels are expanded
|
||||||
* by this ratio so that the output frame's pixels have a ratio of 1.
|
* by this ratio so that the output frame's pixels have a ratio of 1.
|
||||||
* @param inputWidth The input frame width, in pixels.
|
|
||||||
* @param inputHeight The input frame height, in pixels.
|
|
||||||
* @param effects The {@link GlEffect GlEffects} to apply to each frame.
|
* @param effects The {@link GlEffect GlEffects} to apply to each frame.
|
||||||
* @param outputSurfaceProvider A {@link SurfaceInfo.Provider} managing the output {@link
|
* @param outputSurfaceProvider A {@link SurfaceInfo.Provider} managing the output {@link
|
||||||
* Surface}.
|
* Surface}.
|
||||||
@ -66,16 +65,12 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
Context context,
|
Context context,
|
||||||
FrameProcessor.Listener listener,
|
FrameProcessor.Listener listener,
|
||||||
float pixelWidthHeightRatio,
|
float pixelWidthHeightRatio,
|
||||||
int inputWidth,
|
|
||||||
int inputHeight,
|
|
||||||
long streamOffsetUs,
|
long streamOffsetUs,
|
||||||
List<GlEffect> effects,
|
List<GlEffect> effects,
|
||||||
SurfaceInfo.Provider outputSurfaceProvider,
|
SurfaceInfo.Provider outputSurfaceProvider,
|
||||||
Transformer.DebugViewProvider debugViewProvider,
|
Transformer.DebugViewProvider debugViewProvider,
|
||||||
boolean enableExperimentalHdrEditing)
|
boolean enableExperimentalHdrEditing)
|
||||||
throws FrameProcessingException {
|
throws FrameProcessingException {
|
||||||
checkArgument(inputWidth > 0, "inputWidth must be positive");
|
|
||||||
checkArgument(inputHeight > 0, "inputHeight must be positive");
|
|
||||||
|
|
||||||
ExecutorService singleThreadExecutorService = Util.newSingleThreadExecutor(THREAD_NAME);
|
ExecutorService singleThreadExecutorService = Util.newSingleThreadExecutor(THREAD_NAME);
|
||||||
|
|
||||||
@ -86,8 +81,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
context,
|
context,
|
||||||
listener,
|
listener,
|
||||||
pixelWidthHeightRatio,
|
pixelWidthHeightRatio,
|
||||||
inputWidth,
|
|
||||||
inputHeight,
|
|
||||||
streamOffsetUs,
|
streamOffsetUs,
|
||||||
effects,
|
effects,
|
||||||
outputSurfaceProvider,
|
outputSurfaceProvider,
|
||||||
@ -118,8 +111,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
Context context,
|
Context context,
|
||||||
FrameProcessor.Listener listener,
|
FrameProcessor.Listener listener,
|
||||||
float pixelWidthHeightRatio,
|
float pixelWidthHeightRatio,
|
||||||
int inputWidth,
|
|
||||||
int inputHeight,
|
|
||||||
long streamOffsetUs,
|
long streamOffsetUs,
|
||||||
List<GlEffect> effects,
|
List<GlEffect> effects,
|
||||||
SurfaceInfo.Provider outputSurfaceProvider,
|
SurfaceInfo.Provider outputSurfaceProvider,
|
||||||
@ -177,8 +168,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
eglContext,
|
eglContext,
|
||||||
frameProcessingTaskExecutor,
|
frameProcessingTaskExecutor,
|
||||||
streamOffsetUs,
|
streamOffsetUs,
|
||||||
/* inputExternalTexture= */ new TextureInfo(
|
/* inputExternalTextureId= */ GlUtil.createExternalTexture(),
|
||||||
GlUtil.createExternalTexture(), /* fboId= */ C.INDEX_UNSET, inputWidth, inputHeight),
|
|
||||||
externalTextureProcessor,
|
externalTextureProcessor,
|
||||||
textureProcessors);
|
textureProcessors);
|
||||||
}
|
}
|
||||||
@ -300,21 +290,19 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
* timestamps, in microseconds.
|
* timestamps, in microseconds.
|
||||||
*/
|
*/
|
||||||
private final long streamOffsetUs;
|
private final long streamOffsetUs;
|
||||||
/**
|
|
||||||
* Number of frames {@linkplain #registerInputFrame() registered} but not processed off the {@link
|
|
||||||
* #inputSurfaceTexture} yet.
|
|
||||||
*/
|
|
||||||
private final AtomicInteger pendingInputFrameCount;
|
|
||||||
/** Associated with an OpenGL external texture. */
|
/** Associated with an OpenGL external texture. */
|
||||||
private final SurfaceTexture inputSurfaceTexture;
|
private final SurfaceTexture inputSurfaceTexture;
|
||||||
/** Wraps the {@link #inputSurfaceTexture}. */
|
/** Wraps the {@link #inputSurfaceTexture}. */
|
||||||
private final Surface inputSurface;
|
private final Surface inputSurface;
|
||||||
|
|
||||||
private final float[] inputSurfaceTextureTransformMatrix;
|
private final float[] inputSurfaceTextureTransformMatrix;
|
||||||
private final TextureInfo inputExternalTexture;
|
private final int inputExternalTextureId;
|
||||||
private final ExternalTextureProcessor inputExternalTextureProcessor;
|
private final ExternalTextureProcessor inputExternalTextureProcessor;
|
||||||
private final ImmutableList<GlTextureProcessor> textureProcessors;
|
private final ImmutableList<GlTextureProcessor> textureProcessors;
|
||||||
|
private final ConcurrentLinkedQueue<FrameInfo> pendingInputFrames;
|
||||||
|
|
||||||
|
private @MonotonicNonNull FrameInfo nextInputFrameInfo;
|
||||||
private boolean inputStreamEnded;
|
private boolean inputStreamEnded;
|
||||||
|
|
||||||
private GlEffectsFrameProcessor(
|
private GlEffectsFrameProcessor(
|
||||||
@ -322,7 +310,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
EGLContext eglContext,
|
EGLContext eglContext,
|
||||||
FrameProcessingTaskExecutor frameProcessingTaskExecutor,
|
FrameProcessingTaskExecutor frameProcessingTaskExecutor,
|
||||||
long streamOffsetUs,
|
long streamOffsetUs,
|
||||||
TextureInfo inputExternalTexture,
|
int inputExternalTextureId,
|
||||||
ExternalTextureProcessor inputExternalTextureProcessor,
|
ExternalTextureProcessor inputExternalTextureProcessor,
|
||||||
ImmutableList<GlTextureProcessor> textureProcessors) {
|
ImmutableList<GlTextureProcessor> textureProcessors) {
|
||||||
checkState(!textureProcessors.isEmpty());
|
checkState(!textureProcessors.isEmpty());
|
||||||
@ -331,33 +319,40 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
this.eglContext = eglContext;
|
this.eglContext = eglContext;
|
||||||
this.frameProcessingTaskExecutor = frameProcessingTaskExecutor;
|
this.frameProcessingTaskExecutor = frameProcessingTaskExecutor;
|
||||||
this.streamOffsetUs = streamOffsetUs;
|
this.streamOffsetUs = streamOffsetUs;
|
||||||
this.inputExternalTexture = inputExternalTexture;
|
this.inputExternalTextureId = inputExternalTextureId;
|
||||||
this.inputExternalTextureProcessor = inputExternalTextureProcessor;
|
this.inputExternalTextureProcessor = inputExternalTextureProcessor;
|
||||||
this.textureProcessors = textureProcessors;
|
this.textureProcessors = textureProcessors;
|
||||||
|
|
||||||
pendingInputFrameCount = new AtomicInteger();
|
inputSurfaceTexture = new SurfaceTexture(inputExternalTextureId);
|
||||||
inputSurfaceTexture = new SurfaceTexture(inputExternalTexture.texId);
|
|
||||||
inputSurface = new Surface(inputSurfaceTexture);
|
inputSurface = new Surface(inputSurfaceTexture);
|
||||||
inputSurfaceTextureTransformMatrix = new float[16];
|
inputSurfaceTextureTransformMatrix = new float[16];
|
||||||
|
pendingInputFrames = new ConcurrentLinkedQueue<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Surface getInputSurface() {
|
public Surface getInputSurface() {
|
||||||
// TODO(b/227625423): Allow input surface to be recreated for input size change.
|
|
||||||
inputSurfaceTexture.setOnFrameAvailableListener(
|
inputSurfaceTexture.setOnFrameAvailableListener(
|
||||||
surfaceTexture -> frameProcessingTaskExecutor.submit(this::processInputFrame));
|
surfaceTexture -> frameProcessingTaskExecutor.submit(this::processInputFrame));
|
||||||
return inputSurface;
|
return inputSurface;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setInputFrameInfo(FrameInfo inputFrameInfo) {
|
||||||
|
nextInputFrameInfo = inputFrameInfo;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerInputFrame() {
|
public void registerInputFrame() {
|
||||||
checkState(!inputStreamEnded);
|
checkState(!inputStreamEnded);
|
||||||
pendingInputFrameCount.incrementAndGet();
|
checkStateNotNull(
|
||||||
|
nextInputFrameInfo, "setInputFrameInfo must be called before registering input frames");
|
||||||
|
|
||||||
|
pendingInputFrames.add(nextInputFrameInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPendingInputFrameCount() {
|
public int getPendingInputFrameCount() {
|
||||||
return pendingInputFrameCount.get();
|
return pendingInputFrames.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -401,10 +396,15 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
long presentationTimeUs = inputFrameTimeNs / 1000 - streamOffsetUs;
|
long presentationTimeUs = inputFrameTimeNs / 1000 - streamOffsetUs;
|
||||||
inputSurfaceTexture.getTransformMatrix(inputSurfaceTextureTransformMatrix);
|
inputSurfaceTexture.getTransformMatrix(inputSurfaceTextureTransformMatrix);
|
||||||
inputExternalTextureProcessor.setTextureTransformMatrix(inputSurfaceTextureTransformMatrix);
|
inputExternalTextureProcessor.setTextureTransformMatrix(inputSurfaceTextureTransformMatrix);
|
||||||
|
FrameInfo inputFrameInfo = pendingInputFrames.remove();
|
||||||
checkState(
|
checkState(
|
||||||
inputExternalTextureProcessor.maybeQueueInputFrame(
|
inputExternalTextureProcessor.maybeQueueInputFrame(
|
||||||
inputExternalTexture, presentationTimeUs));
|
new TextureInfo(
|
||||||
checkState(pendingInputFrameCount.getAndDecrement() > 0);
|
inputExternalTextureId,
|
||||||
|
/* fboId= */ C.INDEX_UNSET,
|
||||||
|
inputFrameInfo.width,
|
||||||
|
inputFrameInfo.height),
|
||||||
|
presentationTimeUs));
|
||||||
// After the inputExternalTextureProcessor has produced an output frame, it is processed
|
// After the inputExternalTextureProcessor has produced an output frame, it is processed
|
||||||
// asynchronously by the texture processors chained after it.
|
// asynchronously by the texture processors chained after it.
|
||||||
}
|
}
|
||||||
|
@ -120,8 +120,6 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
inputFormat.pixelWidthHeightRatio,
|
inputFormat.pixelWidthHeightRatio,
|
||||||
/* inputWidth= */ decodedWidth,
|
|
||||||
/* inputHeight= */ decodedHeight,
|
|
||||||
streamOffsetUs,
|
streamOffsetUs,
|
||||||
effectsListBuilder.build(),
|
effectsListBuilder.build(),
|
||||||
/* outputSurfaceProvider= */ encoderWrapper,
|
/* outputSurfaceProvider= */ encoderWrapper,
|
||||||
@ -131,6 +129,7 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
throw TransformationException.createForFrameProcessingException(
|
throw TransformationException.createForFrameProcessingException(
|
||||||
e, TransformationException.ERROR_CODE_GL_INIT_FAILED);
|
e, TransformationException.ERROR_CODE_GL_INIT_FAILED);
|
||||||
}
|
}
|
||||||
|
frameProcessor.setInputFrameInfo(new FrameInfo(decodedWidth, decodedHeight));
|
||||||
|
|
||||||
decoder =
|
decoder =
|
||||||
decoderFactory.createForVideoDecoding(
|
decoderFactory.createForVideoDecoding(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user