Use Format object in VideoFrameProcessor

This is to use an existing media3 object rather than creating a new one.

PiperOrigin-RevId: 688481323
This commit is contained in:
kimvde 2024-10-22 03:42:34 -07:00 committed by Copybara-Service
parent 7335754b23
commit be8c58d51e
16 changed files with 251 additions and 272 deletions

View file

@ -12,6 +12,8 @@
* Add `selectedAudioLanguage` parameter to * Add `selectedAudioLanguage` parameter to
`DefaultTrackSelector.selectVideoTrack()` method. `DefaultTrackSelector.selectVideoTrack()` method.
* Transformer: * Transformer:
* Update parameters of `VideoFrameProcessor.registerInputStream` and
`VideoFrameProcessor.Listener.onInputStreamRegistered` to use `Format`.
* Extractors: * Extractors:
* Fix media duration parsing in `mdhd` box of MP4 files to handle `-1` * Fix media duration parsing in `mdhd` box of MP4 files to handle `-1`
values ([#1819](https://github.com/androidx/media/issues/1819)). values ([#1819](https://github.com/androidx/media/issues/1819)).

View file

@ -18,123 +18,34 @@ package androidx.media3.common;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
/** Value class specifying information about a decoded video frame. */ /** Value class specifying information about a decoded video frame. */
@UnstableApi @UnstableApi
public class FrameInfo { public class FrameInfo {
/** A builder for {@link FrameInfo} instances. */
public static final class Builder {
private ColorInfo colorInfo;
private int width;
private int height;
private float pixelWidthHeightRatio;
private long offsetToAddUs;
/**
* Creates an instance with default values.
*
* @param colorInfo The {@link ColorInfo}.
* @param width The frame width, in pixels.
* @param height The frame height, in pixels.
*/
public Builder(ColorInfo colorInfo, int width, int height) {
this.colorInfo = colorInfo;
this.width = width;
this.height = height;
pixelWidthHeightRatio = 1;
}
/** Creates an instance with the values of the provided {@link FrameInfo}. */
public Builder(FrameInfo frameInfo) {
colorInfo = frameInfo.colorInfo;
width = frameInfo.width;
height = frameInfo.height;
pixelWidthHeightRatio = frameInfo.pixelWidthHeightRatio;
offsetToAddUs = frameInfo.offsetToAddUs;
}
/** Sets the {@link ColorInfo}. */
@CanIgnoreReturnValue
public Builder setColorInfo(ColorInfo colorInfo) {
this.colorInfo = colorInfo;
return this;
}
/** Sets the frame width, in pixels. */
@CanIgnoreReturnValue
public Builder setWidth(int width) {
this.width = width;
return this;
}
/** Sets the frame height, in pixels. */
@CanIgnoreReturnValue
public Builder setHeight(int height) {
this.height = height;
return this;
}
/**
* Sets the ratio of width over height for each pixel.
*
* <p>The default value is {@code 1}.
*/
@CanIgnoreReturnValue
public Builder setPixelWidthHeightRatio(float pixelWidthHeightRatio) {
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
return this;
}
/**
* Sets the {@linkplain FrameInfo#offsetToAddUs offset to add} to the frame presentation
* timestamp, in microseconds.
*
* <p>The default value is {@code 0}.
*/
@CanIgnoreReturnValue
public Builder setOffsetToAddUs(long offsetToAddUs) {
this.offsetToAddUs = offsetToAddUs;
return this;
}
/** Builds a {@link FrameInfo} instance. */
public FrameInfo build() {
return new FrameInfo(colorInfo, width, height, pixelWidthHeightRatio, offsetToAddUs);
}
}
/** The {@link ColorInfo} of the frame. */
public final ColorInfo colorInfo;
/** The width of the frame, in pixels. */
public final int width;
/** The height of the frame, in pixels. */
public final int height;
/** The ratio of width over height for each pixel. */
public final float pixelWidthHeightRatio;
/** /**
* The offset that must be added to the frame presentation timestamp, in microseconds. * The {@link Format} of the frame.
* *
* <p>This offset is not part of the input timestamps. It is added to the frame timestamps before * <p>The {@link Format#colorInfo} must be set, and the {@link Format#width} and {@link
* processing, and is retained in the output timestamps. * Format#height} must be greater than 0.
*/ */
public final Format format;
/** The offset that must be added to the frame presentation timestamp, in microseconds. */
public final long offsetToAddUs; public final long offsetToAddUs;
private FrameInfo( /**
ColorInfo colorInfo, int width, int height, float pixelWidthHeightRatio, long offsetToAddUs) { * Creates an instance.
checkArgument(width > 0, "width must be positive, but is: " + width); *
checkArgument(height > 0, "height must be positive, but is: " + height); * @param format See {@link #format}.
* @param offsetToAddUs See {@link #offsetToAddUs}.
*/
public FrameInfo(Format format, long offsetToAddUs) {
checkArgument(format.colorInfo != null, "format colorInfo must be set");
checkArgument(format.width > 0, "format width must be positive, but is: " + format.width);
checkArgument(format.height > 0, "format height must be positive, but is: " + format.height);
this.colorInfo = colorInfo; this.format = format;
this.width = width;
this.height = height;
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
this.offsetToAddUs = offsetToAddUs; this.offsetToAddUs = offsetToAddUs;
} }
} }

View file

@ -84,8 +84,8 @@ public interface VideoFrameProcessor {
* Input frames come from the {@linkplain #getInputSurface input surface} and don't need to be * Input frames come from the {@linkplain #getInputSurface input surface} and don't need to be
* {@linkplain #registerInputFrame registered} (unlike with {@link #INPUT_TYPE_SURFACE}). * {@linkplain #registerInputFrame registered} (unlike with {@link #INPUT_TYPE_SURFACE}).
* *
* <p>Every frame must use the {@linkplain #registerInputStream(int, List, FrameInfo) input * <p>Every frame must use the {@linkplain #registerInputStream input stream's registered} frame
* stream's registered} frame info. Also sets the surface's {@linkplain * format. Also sets the surface's {@linkplain
* android.graphics.SurfaceTexture#setDefaultBufferSize(int, int) default buffer size}. * android.graphics.SurfaceTexture#setDefaultBufferSize(int, int) default buffer size}.
*/ */
int INPUT_TYPE_SURFACE_AUTOMATIC_FRAME_REGISTRATION = 4; int INPUT_TYPE_SURFACE_AUTOMATIC_FRAME_REGISTRATION = 4;
@ -131,8 +131,8 @@ public interface VideoFrameProcessor {
interface Listener { interface Listener {
/** /**
* Called when the {@link VideoFrameProcessor} finishes {@linkplain #registerInputStream(int, * Called when the {@link VideoFrameProcessor} finishes {@linkplain #registerInputStream
* List, FrameInfo) registering an input stream}. * registering an input stream}.
* *
* <p>The {@link VideoFrameProcessor} is now ready to accept new input {@linkplain * <p>The {@link VideoFrameProcessor} is now ready to accept new input {@linkplain
* VideoFrameProcessor#registerInputFrame frames}, {@linkplain * VideoFrameProcessor#registerInputFrame frames}, {@linkplain
@ -140,11 +140,11 @@ public interface VideoFrameProcessor {
* VideoFrameProcessor#queueInputTexture(int, long) textures}. * VideoFrameProcessor#queueInputTexture(int, long) textures}.
* *
* @param inputType The {@link InputType} of the new input stream. * @param inputType The {@link InputType} of the new input stream.
* @param format The {@link Format} of the new input stream.
* @param effects The list of {@link Effect effects} to apply to the new input stream. * @param effects The list of {@link Effect effects} to apply to the new input stream.
* @param frameInfo The {@link FrameInfo} of the new input stream.
*/ */
default void onInputStreamRegistered( default void onInputStreamRegistered(
@InputType int inputType, List<Effect> effects, FrameInfo frameInfo) {} @InputType int inputType, Format format, List<Effect> effects) {}
/** /**
* Called when the output size changes. * Called when the output size changes.
@ -196,8 +196,8 @@ public interface VideoFrameProcessor {
/** /**
* Provides an input {@link Bitmap} to the {@link VideoFrameProcessor}. * Provides an input {@link Bitmap} to the {@link VideoFrameProcessor}.
* *
* <p>Can be called many times after {@link #registerInputStream(int, List, FrameInfo) registering * <p>Can be called many times after {@link #registerInputStream registering the input stream} to
* the input stream} to put multiple frames in the same input stream. * put multiple frames in the same input stream.
* *
* @param inputBitmap The {@link Bitmap} queued to the {@code VideoFrameProcessor}. * @param inputBitmap The {@link Bitmap} queued to the {@code VideoFrameProcessor}.
* @param timestampIterator A {@link TimestampIterator} generating the exact timestamps that the * @param timestampIterator A {@link TimestampIterator} generating the exact timestamps that the
@ -271,14 +271,20 @@ public interface VideoFrameProcessor {
* #queueInputTexture queued}. * #queueInputTexture queued}.
* *
* <p>This method blocks the calling thread until the previous calls to this method finish, that * <p>This method blocks the calling thread until the previous calls to this method finish, that
* is when {@link Listener#onInputStreamRegistered(int, List, FrameInfo)} is called after the * is when {@link Listener#onInputStreamRegistered(int, Format, List)} is called after the
* underlying processing pipeline has been adapted to the registered input stream. * underlying processing pipeline has been adapted to the registered input stream.
* *
* @param inputType The {@link InputType} of the new input stream. * @param inputType The {@link InputType} of the new input stream.
* @param format The {@link Format} of the new input stream. The {@link Format#colorInfo}, the
* {@link Format#width}, the {@link Format#height} and the {@link
* Format#pixelWidthHeightRatio} must be set.
* @param effects The list of {@link Effect effects} to apply to the new input stream. * @param effects The list of {@link Effect effects} to apply to the new input stream.
* @param frameInfo The {@link FrameInfo} of the new input stream. * @param offsetToAddUs The offset that must be added to the frame presentation timestamps, in
* microseconds. This offset is not part of the input timestamps. It is added to the frame
* timestamps before processing, and is retained in the output timestamps.
*/ */
void registerInputStream(@InputType int inputType, List<Effect> effects, FrameInfo frameInfo); void registerInputStream(
@InputType int inputType, Format format, List<Effect> effects, long offsetToAddUs);
/** /**
* Informs the {@code VideoFrameProcessor} that a frame will be queued to its {@linkplain * Informs the {@code VideoFrameProcessor} that a frame will be queued to its {@linkplain
@ -287,11 +293,10 @@ public interface VideoFrameProcessor {
* <p>Must be called before rendering a frame to the input surface. The caller must not render * <p>Must be called before rendering a frame to the input surface. The caller must not render
* frames to the {@linkplain #getInputSurface input surface} when {@code false} is returned. * frames to the {@linkplain #getInputSurface input surface} when {@code false} is returned.
* *
* @return Whether the input frame was successfully registered. If {@link * @return Whether the input frame was successfully registered. If {@link #registerInputStream} is
* #registerInputStream(int, List, FrameInfo)} is called, this method returns {@code false} * called, this method returns {@code false} until {@link
* until {@link Listener#onInputStreamRegistered(int, List, FrameInfo)} is called. Otherwise, * Listener#onInputStreamRegistered(int, Format, List)} is called. Otherwise, a return value
* a return value of {@code false} indicates the {@code VideoFrameProcessor} is not ready to * of {@code false} indicates the {@code VideoFrameProcessor} is not ready to accept input.
* accept input.
* @throws UnsupportedOperationException If the {@code VideoFrameProcessor} does not accept * @throws UnsupportedOperationException If the {@code VideoFrameProcessor} does not accept
* {@linkplain #INPUT_TYPE_SURFACE surface input}. * {@linkplain #INPUT_TYPE_SURFACE surface input}.
* @throws IllegalStateException If called after {@link #signalEndOfInput()} or before {@link * @throws IllegalStateException If called after {@link #signalEndOfInput()} or before {@link

View file

@ -30,7 +30,7 @@ import androidx.media3.common.C;
import androidx.media3.common.ColorInfo; import androidx.media3.common.ColorInfo;
import androidx.media3.common.DebugViewProvider; import androidx.media3.common.DebugViewProvider;
import androidx.media3.common.Effect; import androidx.media3.common.Effect;
import androidx.media3.common.FrameInfo; import androidx.media3.common.Format;
import androidx.media3.common.VideoFrameProcessingException; import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.VideoFrameProcessor; import androidx.media3.common.VideoFrameProcessor;
import androidx.media3.common.util.ConditionVariable; import androidx.media3.common.util.ConditionVariable;
@ -43,6 +43,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
@ -90,8 +91,8 @@ public class DefaultVideoFrameProcessorTest {
@Override @Override
public void onInputStreamRegistered( public void onInputStreamRegistered(
@VideoFrameProcessor.InputType int inputType, @VideoFrameProcessor.InputType int inputType,
List<Effect> effects, Format format,
FrameInfo frameInfo) { List<Effect> effects) {
inputStreamRegisteredCountDownLatch.countDown(); inputStreamRegisteredCountDownLatch.countDown();
} }
@ -115,9 +116,13 @@ public class DefaultVideoFrameProcessorTest {
}); });
defaultVideoFrameProcessor.registerInputStream( defaultVideoFrameProcessor.registerInputStream(
VideoFrameProcessor.INPUT_TYPE_BITMAP, VideoFrameProcessor.INPUT_TYPE_BITMAP,
new Format.Builder()
.setColorInfo(ColorInfo.SRGB_BT709_FULL)
.setWidth(100)
.setHeight(100)
.build(),
ImmutableList.of(), ImmutableList.of(),
new FrameInfo.Builder(ColorInfo.SRGB_BT709_FULL, /* width= */ 100, /* height= */ 100) /* offsetToAddUs= */ 0);
.build());
assertThat(defaultVideoFrameProcessor.getPendingInputFrameCount()).isEqualTo(0); assertThat(defaultVideoFrameProcessor.getPendingInputFrameCount()).isEqualTo(0);
// Unblocks configuration. // Unblocks configuration.
@ -141,10 +146,10 @@ public class DefaultVideoFrameProcessorTest {
@Override @Override
public void onInputStreamRegistered( public void onInputStreamRegistered(
@VideoFrameProcessor.InputType int inputType, @VideoFrameProcessor.InputType int inputType,
List<Effect> effects, Format format,
FrameInfo frameInfo) { List<Effect> effects) {
registeredInputStreamInfoWidths.add( registeredInputStreamInfoWidths.add(
new InputStreamInfo(inputType, effects, frameInfo)); new InputStreamInfo(inputType, format, effects));
countDownLatch.countDown(); countDownLatch.countDown();
} }
@ -157,21 +162,30 @@ public class DefaultVideoFrameProcessorTest {
InputStreamInfo stream1 = InputStreamInfo stream1 =
new InputStreamInfo( new InputStreamInfo(
VideoFrameProcessor.INPUT_TYPE_BITMAP, VideoFrameProcessor.INPUT_TYPE_BITMAP,
ImmutableList.of(), new Format.Builder()
new FrameInfo.Builder(ColorInfo.SRGB_BT709_FULL, /* width= */ 100, /* height= */ 100) .setColorInfo(ColorInfo.SRGB_BT709_FULL)
.build()); .setWidth(100)
.setHeight(100)
.build(),
ImmutableList.of());
InputStreamInfo stream2 = InputStreamInfo stream2 =
new InputStreamInfo( new InputStreamInfo(
VideoFrameProcessor.INPUT_TYPE_BITMAP, VideoFrameProcessor.INPUT_TYPE_BITMAP,
ImmutableList.of(new Contrast(.5f)), new Format.Builder()
new FrameInfo.Builder(ColorInfo.SRGB_BT709_FULL, /* width= */ 200, /* height= */ 200) .setColorInfo(ColorInfo.SRGB_BT709_FULL)
.build()); .setWidth(200)
.setHeight(200)
.build(),
ImmutableList.of(new Contrast(.5f)));
InputStreamInfo stream3 = InputStreamInfo stream3 =
new InputStreamInfo( new InputStreamInfo(
VideoFrameProcessor.INPUT_TYPE_BITMAP, VideoFrameProcessor.INPUT_TYPE_BITMAP,
ImmutableList.of(), new Format.Builder()
new FrameInfo.Builder(ColorInfo.SRGB_BT709_FULL, /* width= */ 300, /* height= */ 300) .setColorInfo(ColorInfo.SRGB_BT709_FULL)
.build()); .setWidth(300)
.setHeight(300)
.build(),
ImmutableList.of());
registerInputStream(defaultVideoFrameProcessor, stream1); registerInputStream(defaultVideoFrameProcessor, stream1);
registerInputStream(defaultVideoFrameProcessor, stream2); registerInputStream(defaultVideoFrameProcessor, stream2);
@ -207,8 +221,8 @@ public class DefaultVideoFrameProcessorTest {
@Override @Override
public void onInputStreamRegistered( public void onInputStreamRegistered(
@VideoFrameProcessor.InputType int inputType, @VideoFrameProcessor.InputType int inputType,
List<Effect> effects, Format format,
FrameInfo frameInfo) { List<Effect> effects) {
inputStreamRegisteredCondition.open(); inputStreamRegisteredCondition.open();
} }
@ -241,9 +255,13 @@ public class DefaultVideoFrameProcessorTest {
inputStreamRegisteredCondition.close(); inputStreamRegisteredCondition.close();
defaultVideoFrameProcessor.registerInputStream( defaultVideoFrameProcessor.registerInputStream(
VideoFrameProcessor.INPUT_TYPE_BITMAP, VideoFrameProcessor.INPUT_TYPE_BITMAP,
new Format.Builder()
.setColorInfo(ColorInfo.SRGB_BT709_FULL)
.setWidth(bitmap1.getWidth())
.setHeight(bitmap1.getHeight())
.build(),
ImmutableList.of(), ImmutableList.of(),
new FrameInfo.Builder(ColorInfo.SRGB_BT709_FULL, bitmap1.getWidth(), bitmap1.getHeight()) /* offsetToAddUs= */ 0);
.build());
inputStreamRegisteredCondition.block(); inputStreamRegisteredCondition.block();
defaultVideoFrameProcessor.queueInputBitmap( defaultVideoFrameProcessor.queueInputBitmap(
bitmap1, new ConstantRateTimestampIterator(C.MICROS_PER_SECOND, 30.f)); bitmap1, new ConstantRateTimestampIterator(C.MICROS_PER_SECOND, 30.f));
@ -252,14 +270,18 @@ public class DefaultVideoFrameProcessorTest {
inputStreamRegisteredCondition.close(); inputStreamRegisteredCondition.close();
defaultVideoFrameProcessor.registerInputStream( defaultVideoFrameProcessor.registerInputStream(
VideoFrameProcessor.INPUT_TYPE_BITMAP, VideoFrameProcessor.INPUT_TYPE_BITMAP,
new Format.Builder()
.setColorInfo(ColorInfo.SRGB_BT709_FULL)
.setWidth(bitmap2.getWidth())
.setHeight(bitmap2.getHeight())
.build(),
ImmutableList.of( ImmutableList.of(
(GlEffect) (GlEffect)
(context, useHdr) -> { (context, useHdr) -> {
secondStreamConfigurationTimeMs.set(SystemClock.DEFAULT.elapsedRealtime()); secondStreamConfigurationTimeMs.set(SystemClock.DEFAULT.elapsedRealtime());
return new PassthroughShaderProgram(); return new PassthroughShaderProgram();
}), }),
new FrameInfo.Builder(ColorInfo.SRGB_BT709_FULL, bitmap2.getWidth(), bitmap2.getHeight()) /* offsetToAddUs= */ 0);
.build());
inputStreamRegisteredCondition.block(); inputStreamRegisteredCondition.block();
defaultVideoFrameProcessor.queueInputBitmap( defaultVideoFrameProcessor.queueInputBitmap(
bitmap2, new ConstantRateTimestampIterator(C.MICROS_PER_SECOND, 30.f)); bitmap2, new ConstantRateTimestampIterator(C.MICROS_PER_SECOND, 30.f));
@ -287,8 +309,8 @@ public class DefaultVideoFrameProcessorTest {
@Override @Override
public void onInputStreamRegistered( public void onInputStreamRegistered(
@VideoFrameProcessor.InputType int inputType, @VideoFrameProcessor.InputType int inputType,
List<Effect> effects, Format format,
FrameInfo frameInfo) { List<Effect> effects) {
inputStreamRegisteredCountDownLatch.countDown(); inputStreamRegisteredCountDownLatch.countDown();
} }
@ -311,9 +333,13 @@ public class DefaultVideoFrameProcessorTest {
Bitmap bitmap = BitmapPixelTestUtil.readBitmap(ORIGINAL_PNG_ASSET_PATH); Bitmap bitmap = BitmapPixelTestUtil.readBitmap(ORIGINAL_PNG_ASSET_PATH);
defaultVideoFrameProcessor.registerInputStream( defaultVideoFrameProcessor.registerInputStream(
VideoFrameProcessor.INPUT_TYPE_SURFACE_AUTOMATIC_FRAME_REGISTRATION, VideoFrameProcessor.INPUT_TYPE_SURFACE_AUTOMATIC_FRAME_REGISTRATION,
new Format.Builder()
.setColorInfo(ColorInfo.SRGB_BT709_FULL)
.setWidth(bitmap.getWidth())
.setHeight(bitmap.getHeight())
.build(),
/* effects= */ ImmutableList.of(), /* effects= */ ImmutableList.of(),
new FrameInfo.Builder(ColorInfo.SRGB_BT709_FULL, bitmap.getWidth(), bitmap.getHeight()) /* offsetToAddUs= */ 0);
.build());
inputStreamRegisteredCountDownLatch.await(); inputStreamRegisteredCountDownLatch.await();
checkState(defaultVideoFrameProcessor.registerInputFrame()); checkState(defaultVideoFrameProcessor.registerInputFrame());
@ -355,19 +381,22 @@ public class DefaultVideoFrameProcessorTest {
private static void registerInputStream( private static void registerInputStream(
DefaultVideoFrameProcessor defaultVideoFrameProcessor, InputStreamInfo inputStreamInfo) { DefaultVideoFrameProcessor defaultVideoFrameProcessor, InputStreamInfo inputStreamInfo) {
defaultVideoFrameProcessor.registerInputStream( defaultVideoFrameProcessor.registerInputStream(
inputStreamInfo.inputType, inputStreamInfo.effects, inputStreamInfo.frameInfo); inputStreamInfo.inputType,
inputStreamInfo.format,
inputStreamInfo.effects,
/* offsetToAddUs= */ 0);
} }
private static final class InputStreamInfo { private static final class InputStreamInfo {
public final @VideoFrameProcessor.InputType int inputType; public final @VideoFrameProcessor.InputType int inputType;
public final Format format;
public final List<Effect> effects; public final List<Effect> effects;
public final FrameInfo frameInfo;
private InputStreamInfo( private InputStreamInfo(
@VideoFrameProcessor.InputType int inputType, List<Effect> effects, FrameInfo frameInfo) { @VideoFrameProcessor.InputType int inputType, Format format, List<Effect> effects) {
this.inputType = inputType; this.inputType = inputType;
this.format = format;
this.effects = effects; this.effects = effects;
this.frameInfo = frameInfo;
} }
@Override @Override
@ -380,16 +409,16 @@ public class DefaultVideoFrameProcessorTest {
} }
InputStreamInfo that = (InputStreamInfo) o; InputStreamInfo that = (InputStreamInfo) o;
return inputType == that.inputType return inputType == that.inputType
&& Util.areEqual(this.effects, that.effects) && Objects.equals(this.format, that.format)
&& Util.areEqual(this.frameInfo, that.frameInfo); && Objects.equals(this.effects, that.effects);
} }
@Override @Override
public int hashCode() { public int hashCode() {
int result = 17; int result = 17;
result = 31 * result + inputType; result = 31 * result + inputType;
result = 31 * result + format.hashCode();
result = 31 * result + effects.hashCode(); result = 31 * result + effects.hashCode();
result = 31 * result + frameInfo.hashCode();
return result; return result;
} }
} }

View file

@ -28,7 +28,7 @@ import androidx.annotation.Nullable;
import androidx.media3.common.ColorInfo; import androidx.media3.common.ColorInfo;
import androidx.media3.common.DebugViewProvider; import androidx.media3.common.DebugViewProvider;
import androidx.media3.common.Effect; import androidx.media3.common.Effect;
import androidx.media3.common.FrameInfo; import androidx.media3.common.Format;
import androidx.media3.common.SurfaceInfo; import androidx.media3.common.SurfaceInfo;
import androidx.media3.common.VideoFrameProcessingException; import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.VideoFrameProcessor; import androidx.media3.common.VideoFrameProcessor;
@ -267,8 +267,8 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
@Override @Override
public void onInputStreamRegistered( public void onInputStreamRegistered(
@VideoFrameProcessor.InputType int inputType, @VideoFrameProcessor.InputType int inputType,
List<Effect> effects, Format format,
FrameInfo frameInfo) { List<Effect> effects) {
videoFrameProcessorReadyCountDownLatch.countDown(); videoFrameProcessorReadyCountDownLatch.countDown();
} }
@ -315,8 +315,13 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
checkNotNull(defaultVideoFrameProcessor) checkNotNull(defaultVideoFrameProcessor)
.registerInputStream( .registerInputStream(
INPUT_TYPE_SURFACE, INPUT_TYPE_SURFACE,
new Format.Builder()
.setColorInfo(ColorInfo.SDR_BT709_LIMITED)
.setWidth(WIDTH)
.setHeight(HEIGHT)
.build(),
/* effects= */ ImmutableList.of((GlEffect) (context, useHdr) -> blankFrameProducer), /* effects= */ ImmutableList.of((GlEffect) (context, useHdr) -> blankFrameProducer),
new FrameInfo.Builder(ColorInfo.SDR_BT709_LIMITED, WIDTH, HEIGHT).build()); /* offsetToAddUs= */ 0);
boolean testTimedOut = false; boolean testTimedOut = false;
if (!videoFrameProcessorReadyCountDownLatch.await(TEST_TIMEOUT_MS, MILLISECONDS)) { if (!videoFrameProcessorReadyCountDownLatch.await(TEST_TIMEOUT_MS, MILLISECONDS)) {
testTimedOut = true; testTimedOut = true;

View file

@ -31,7 +31,7 @@ import androidx.annotation.Nullable;
import androidx.media3.common.ColorInfo; import androidx.media3.common.ColorInfo;
import androidx.media3.common.DebugViewProvider; import androidx.media3.common.DebugViewProvider;
import androidx.media3.common.Effect; import androidx.media3.common.Effect;
import androidx.media3.common.FrameInfo; import androidx.media3.common.Format;
import androidx.media3.common.VideoFrameProcessingException; import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.VideoFrameProcessor; import androidx.media3.common.VideoFrameProcessor;
import androidx.media3.common.util.Consumer; import androidx.media3.common.util.Consumer;
@ -133,8 +133,8 @@ import java.util.concurrent.atomic.AtomicReference;
@Override @Override
public void onInputStreamRegistered( public void onInputStreamRegistered(
@VideoFrameProcessor.InputType int inputType, @VideoFrameProcessor.InputType int inputType,
List<Effect> effects, Format format,
FrameInfo frameInfo) { List<Effect> effects) {
videoFrameProcessorReadyCountDownLatch.countDown(); videoFrameProcessorReadyCountDownLatch.countDown();
} }
@ -162,6 +162,11 @@ import java.util.concurrent.atomic.AtomicReference;
checkNotNull(defaultVideoFrameProcessor) checkNotNull(defaultVideoFrameProcessor)
.registerInputStream( .registerInputStream(
INPUT_TYPE_SURFACE, INPUT_TYPE_SURFACE,
new Format.Builder()
.setColorInfo(ColorInfo.SDR_BT709_LIMITED)
.setWidth(frameWidth)
.setHeight(frameHeight)
.build(),
/* effects= */ ImmutableList.of( /* effects= */ ImmutableList.of(
(GlEffect) (context, useHdr) -> blankFrameProducer, (GlEffect) (context, useHdr) -> blankFrameProducer,
// Use an overlay effect to generate bitmaps with timestamps on it. // Use an overlay effect to generate bitmaps with timestamps on it.
@ -177,7 +182,7 @@ import java.util.concurrent.atomic.AtomicReference;
} }
})), })),
glEffect), glEffect),
new FrameInfo.Builder(ColorInfo.SDR_BT709_LIMITED, frameWidth, frameHeight).build()); /* offsetToAddUs= */ 0);
videoFrameProcessorReadyCountDownLatch.await(); videoFrameProcessorReadyCountDownLatch.await();
checkNoVideoFrameProcessingExceptionIsThrown(videoFrameProcessingExceptionReference); checkNoVideoFrameProcessingExceptionIsThrown(videoFrameProcessingExceptionReference);
blankFrameProducer.produceBlankFrames(presentationTimesUs); blankFrameProducer.produceBlankFrames(presentationTimesUs);

View file

@ -169,8 +169,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
EVENT_QUEUE_BITMAP, EVENT_QUEUE_BITMAP,
currentPresentationTimeUs, currentPresentationTimeUs,
/* extraFormat= */ "%dx%d", /* extraFormat= */ "%dx%d",
/* extraArgs...= */ currentFrameInfo.width, /* extraArgs...= */ currentFrameInfo.format.width,
currentFrameInfo.height); currentFrameInfo.format.height);
if (!currentBitmapInfo.inStreamOffsetsUs.hasNext()) { if (!currentBitmapInfo.inStreamOffsetsUs.hasNext()) {
isNextFrameInTexture = false; isNextFrameInTexture = false;
@ -216,8 +216,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
currentTexId, currentTexId,
/* fboId= */ C.INDEX_UNSET, /* fboId= */ C.INDEX_UNSET,
/* rboId= */ C.INDEX_UNSET, /* rboId= */ C.INDEX_UNSET,
frameInfo.width, frameInfo.format.width,
frameInfo.height); frameInfo.format.height);
if (Util.SDK_INT >= 34 && bitmap.hasGainmap()) { if (Util.SDK_INT >= 34 && bitmap.hasGainmap()) {
checkNotNull(repeatingGainmapShaderProgram).setGainmap(checkNotNull(bitmap.getGainmap())); checkNotNull(repeatingGainmapShaderProgram).setGainmap(checkNotNull(bitmap.getGainmap()));
} }

View file

@ -46,6 +46,7 @@ import androidx.media3.common.C;
import androidx.media3.common.ColorInfo; import androidx.media3.common.ColorInfo;
import androidx.media3.common.DebugViewProvider; import androidx.media3.common.DebugViewProvider;
import androidx.media3.common.Effect; import androidx.media3.common.Effect;
import androidx.media3.common.Format;
import androidx.media3.common.FrameInfo; import androidx.media3.common.FrameInfo;
import androidx.media3.common.GlObjectsProvider; import androidx.media3.common.GlObjectsProvider;
import androidx.media3.common.MediaLibraryInfo; import androidx.media3.common.MediaLibraryInfo;
@ -201,16 +202,16 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
* change between input streams is handled frame-exactly. If {@code false}, {@link * change between input streams is handled frame-exactly. If {@code false}, {@link
* #registerInputFrame} can be called only once for each {@linkplain #registerInputStream * #registerInputFrame} can be called only once for each {@linkplain #registerInputStream
* registered input stream} before rendering the first frame to the input {@link * registered input stream} before rendering the first frame to the input {@link
* #getInputSurface() Surface}. The same registered {@link FrameInfo} is repeated for the * #getInputSurface() Surface}. The same registered {@link Format} is repeated for the
* subsequent frames. To ensure the format change between input streams is applied on the * subsequent frames. To ensure the format change between input streams is applied on the
* right frame, the caller needs to {@linkplain #registerInputStream(int, List, FrameInfo) * right frame, the caller needs to {@linkplain #registerInputStream register} the new input
* register} the new input stream strictly after rendering all frames from the previous input * stream strictly after rendering all frames from the previous input stream. This mode should
* stream. This mode should be used in streams where users don't have direct control over * be used in streams where users don't have direct control over rendering frames, like in a
* rendering frames, like in a camera feed. * camera feed.
* *
* <p>Regardless of the value set, {@link #registerInputStream(int, List, FrameInfo)} must be * <p>Regardless of the value set, {@link #registerInputStream} must be called for each input
* called for each input stream to specify the format for upcoming frames before calling * stream to specify the format for upcoming frames before calling {@link
* {@link #registerInputFrame()}. * #registerInputFrame()}.
* *
* @param requireRegisteringAllInputFrames Whether registering every input frame is required. * @param requireRegisteringAllInputFrames Whether registering every input frame is required.
* @deprecated For automatic frame registration ({@code * @deprecated For automatic frame registration ({@code
@ -457,8 +458,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
private final ConditionVariable inputStreamRegisteredCondition; private final ConditionVariable inputStreamRegisteredCondition;
/** /**
* The input stream that is {@linkplain #registerInputStream(int, List, FrameInfo) registered}, * The input stream that is {@linkplain #registerInputStream registered}, but the pipeline has not
* but the pipeline has not adapted to processing it. * adapted to processing it.
*/ */
@GuardedBy("lock") @GuardedBy("lock")
@Nullable @Nullable
@ -567,10 +568,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
FrameInfo frameInfo = checkNotNull(this.nextInputFrameInfo); FrameInfo frameInfo = checkNotNull(this.nextInputFrameInfo);
inputSwitcher inputSwitcher
.activeTextureManager() .activeTextureManager()
.queueInputBitmap( .queueInputBitmap(inputBitmap, frameInfo, timestampIterator);
inputBitmap,
new FrameInfo.Builder(frameInfo).setOffsetToAddUs(frameInfo.offsetToAddUs).build(),
timestampIterator);
return true; return true;
} }
@ -609,18 +607,18 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* <p>Using HDR {@link FrameInfo#colorInfo} requires OpenGL ES 3.0 and the {@code EXT_YUV_target} * <p>Using HDR {@link Format#colorInfo} requires OpenGL ES 3.0 and the {@code EXT_YUV_target}
* OpenGL extension. * OpenGL extension.
* *
* <p>{@link Effect}s are applied on {@link C#COLOR_RANGE_FULL} colors with {@code null} {@link * <p>{@link Effect}s are applied on {@link C#COLOR_RANGE_FULL} colors with {@code null} {@link
* ColorInfo#hdrStaticInfo}. * ColorInfo#hdrStaticInfo}.
* *
* <p>If either {@link FrameInfo#colorInfo} or {@code outputColorInfo} {@linkplain * <p>If either {@link Format#colorInfo} or {@code outputColorInfo} {@linkplain
* ColorInfo#isTransferHdr} are HDR}, textures will use {@link GLES30#GL_RGBA16F} and {@link * ColorInfo#isTransferHdr} are HDR}, textures will use {@link GLES30#GL_RGBA16F} and {@link
* GLES30#GL_HALF_FLOAT}. Otherwise, textures will use {@link GLES20#GL_RGBA} and {@link * GLES30#GL_HALF_FLOAT}. Otherwise, textures will use {@link GLES20#GL_RGBA} and {@link
* GLES20#GL_UNSIGNED_BYTE}. * GLES20#GL_UNSIGNED_BYTE}.
* *
* <p>If {@linkplain FrameInfo#colorInfo input color} {@linkplain ColorInfo#isTransferHdr is HDR}, * <p>If {@linkplain Format#colorInfo input color} {@linkplain ColorInfo#isTransferHdr is HDR},
* but {@code outputColorInfo} is SDR, then HDR to SDR tone-mapping is applied, and {@code * but {@code outputColorInfo} is SDR, then HDR to SDR tone-mapping is applied, and {@code
* outputColorInfo}'s {@link ColorInfo#colorTransfer} must be {@link C#COLOR_TRANSFER_GAMMA_2_2} * outputColorInfo}'s {@link ColorInfo#colorTransfer} must be {@link C#COLOR_TRANSFER_GAMMA_2_2}
* or {@link C#COLOR_TRANSFER_SDR}. In this case, the actual output transfer function will be in * or {@link C#COLOR_TRANSFER_SDR}. In this case, the actual output transfer function will be in
@ -630,18 +628,19 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
*/ */
@Override @Override
public void registerInputStream( public void registerInputStream(
@InputType int inputType, List<Effect> effects, FrameInfo frameInfo) { @InputType int inputType, Format format, List<Effect> effects, long offsetToAddUs) {
// This method is only called after all samples in the current input stream are registered or // This method is only called after all samples in the current input stream are registered or
// queued. // queued.
DebugTraceUtil.logEvent( DebugTraceUtil.logEvent(
COMPONENT_VFP, COMPONENT_VFP,
EVENT_REGISTER_NEW_INPUT_STREAM, EVENT_REGISTER_NEW_INPUT_STREAM,
/* presentationTimeUs= */ frameInfo.offsetToAddUs, /* presentationTimeUs= */ offsetToAddUs,
/* extraFormat= */ "InputType %s - %dx%d", /* extraFormat= */ "InputType %s - %dx%d",
/* extraArgs...= */ getInputTypeString(inputType), /* extraArgs...= */ getInputTypeString(inputType),
frameInfo.width, format.width,
frameInfo.height); format.height);
nextInputFrameInfo = adjustForPixelWidthHeightRatio(frameInfo); Format nextFormat = adjustForPixelWidthHeightRatio(format);
nextInputFrameInfo = new FrameInfo(nextFormat, offsetToAddUs);
try { try {
// Blocks until the previous input stream registration completes. // Blocks until the previous input stream registration completes.
@ -654,7 +653,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
synchronized (lock) { synchronized (lock) {
// An input stream is pending until its effects are configured. // An input stream is pending until its effects are configured.
InputStreamInfo pendingInputStreamInfo = new InputStreamInfo(inputType, effects, frameInfo); InputStreamInfo pendingInputStreamInfo =
new InputStreamInfo(inputType, format, effects, offsetToAddUs);
if (!registeredFirstInputStream) { if (!registeredFirstInputStream) {
registeredFirstInputStream = true; registeredFirstInputStream = true;
inputStreamRegisteredCondition.close(); inputStreamRegisteredCondition.close();
@ -770,23 +770,24 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
} }
/** /**
* Expands the frame based on the {@link FrameInfo#pixelWidthHeightRatio} and returns a new {@link * Expands the frame based on the {@link Format#pixelWidthHeightRatio} and returns a new {@link
* FrameInfo} instance with scaled dimensions and {@link FrameInfo#pixelWidthHeightRatio} of * Format} instance with scaled dimensions and {@link Format#pixelWidthHeightRatio} of {@code 1}.
* {@code 1}.
*/ */
private FrameInfo adjustForPixelWidthHeightRatio(FrameInfo frameInfo) { private Format adjustForPixelWidthHeightRatio(Format format) {
if (frameInfo.pixelWidthHeightRatio > 1f) { if (format.pixelWidthHeightRatio > 1f) {
return new FrameInfo.Builder(frameInfo) return format
.setWidth((int) (frameInfo.width * frameInfo.pixelWidthHeightRatio)) .buildUpon()
.setWidth((int) (format.width * format.pixelWidthHeightRatio))
.setPixelWidthHeightRatio(1) .setPixelWidthHeightRatio(1)
.build(); .build();
} else if (frameInfo.pixelWidthHeightRatio < 1f) { } else if (format.pixelWidthHeightRatio < 1f) {
return new FrameInfo.Builder(frameInfo) return format
.setHeight((int) (frameInfo.height / frameInfo.pixelWidthHeightRatio)) .buildUpon()
.setHeight((int) (format.height / format.pixelWidthHeightRatio))
.setPixelWidthHeightRatio(1) .setPixelWidthHeightRatio(1)
.build(); .build();
} else { } else {
return frameInfo; return format;
} }
} }
@ -995,7 +996,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
*/ */
private void configureEffects(InputStreamInfo inputStreamInfo, boolean forceReconfigure) private void configureEffects(InputStreamInfo inputStreamInfo, boolean forceReconfigure)
throws VideoFrameProcessingException { throws VideoFrameProcessingException {
checkColors(/* inputColorInfo= */ inputStreamInfo.frameInfo.colorInfo, outputColorInfo); checkColors(
/* inputColorInfo= */ checkNotNull(inputStreamInfo.format.colorInfo), outputColorInfo);
if (forceReconfigure || !activeEffects.equals(inputStreamInfo.effects)) { if (forceReconfigure || !activeEffects.equals(inputStreamInfo.effects)) {
if (!intermediateGlShaderPrograms.isEmpty()) { if (!intermediateGlShaderPrograms.isEmpty()) {
for (int i = 0; i < intermediateGlShaderPrograms.size(); i++) { for (int i = 0; i < intermediateGlShaderPrograms.size(); i++) {
@ -1023,7 +1025,9 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
activeEffects.addAll(inputStreamInfo.effects); activeEffects.addAll(inputStreamInfo.effects);
} }
inputSwitcher.switchToInput(inputStreamInfo.inputType, inputStreamInfo.frameInfo); inputSwitcher.switchToInput(
inputStreamInfo.inputType,
new FrameInfo(inputStreamInfo.format, inputStreamInfo.offsetToAddUs));
inputStreamRegisteredCondition.open(); inputStreamRegisteredCondition.open();
synchronized (lock) { synchronized (lock) {
if (onInputSurfaceReadyListener != null) { if (onInputSurfaceReadyListener != null) {
@ -1034,7 +1038,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
listenerExecutor.execute( listenerExecutor.execute(
() -> () ->
listener.onInputStreamRegistered( listener.onInputStreamRegistered(
inputStreamInfo.inputType, inputStreamInfo.effects, inputStreamInfo.frameInfo)); inputStreamInfo.inputType, inputStreamInfo.format, inputStreamInfo.effects));
} }
/** Checks that color configuration is valid for {@link DefaultVideoFrameProcessor}. */ /** Checks that color configuration is valid for {@link DefaultVideoFrameProcessor}. */
@ -1151,13 +1155,16 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
private static final class InputStreamInfo { private static final class InputStreamInfo {
public final @InputType int inputType; public final @InputType int inputType;
public final Format format;
public final List<Effect> effects; public final List<Effect> effects;
public final FrameInfo frameInfo; public final long offsetToAddUs;
public InputStreamInfo(@InputType int inputType, List<Effect> effects, FrameInfo frameInfo) { public InputStreamInfo(
@InputType int inputType, Format format, List<Effect> effects, long offsetToAddUs) {
this.inputType = inputType; this.inputType = inputType;
this.format = format;
this.effects = effects; this.effects = effects;
this.frameInfo = frameInfo; this.offsetToAddUs = offsetToAddUs;
} }
} }
} }

View file

@ -230,7 +230,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
repeatLastRegisteredFrame = automaticReregistration; repeatLastRegisteredFrame = automaticReregistration;
if (repeatLastRegisteredFrame) { if (repeatLastRegisteredFrame) {
lastRegisteredFrame = inputFrameInfo; lastRegisteredFrame = inputFrameInfo;
surfaceTexture.setDefaultBufferSize(inputFrameInfo.width, inputFrameInfo.height); surfaceTexture.setDefaultBufferSize(
inputFrameInfo.format.width, inputFrameInfo.format.height);
} }
} }
@ -407,7 +408,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
long presentationTimeUs = (frameTimeNs / 1000) + offsetToAddUs; long presentationTimeUs = (frameTimeNs / 1000) + offsetToAddUs;
if (experimentalAdjustSurfaceTextureTransformationMatrix) { if (experimentalAdjustSurfaceTextureTransformationMatrix) {
removeSurfaceTextureScaleFromTransformMatrix( removeSurfaceTextureScaleFromTransformMatrix(
textureTransformMatrix, presentationTimeUs, currentFrame.width, currentFrame.height); textureTransformMatrix,
presentationTimeUs,
currentFrame.format.width,
currentFrame.format.height);
} }
checkNotNull(externalShaderProgram).setTextureTransformMatrix(textureTransformMatrix); checkNotNull(externalShaderProgram).setTextureTransformMatrix(textureTransformMatrix);
@ -418,8 +422,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
externalTexId, externalTexId,
/* fboId= */ C.INDEX_UNSET, /* fboId= */ C.INDEX_UNSET,
/* rboId= */ C.INDEX_UNSET, /* rboId= */ C.INDEX_UNSET,
currentFrame.width, currentFrame.format.width,
currentFrame.height), currentFrame.format.height),
presentationTimeUs); presentationTimeUs);
if (!repeatLastRegisteredFrame) { if (!repeatLastRegisteredFrame) {
checkStateNotNull(pendingFrames.remove()); checkStateNotNull(pendingFrames.remove());

View file

@ -160,11 +160,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// Activate the relevant input for the new input type. // Activate the relevant input for the new input type.
Input input = inputs.get(newInputType); Input input = inputs.get(newInputType);
if (input.getInputColorInfo() == null ColorInfo newInputColorInfo = checkNotNull(newInputFrameInfo.format.colorInfo);
|| !newInputFrameInfo.colorInfo.equals(input.getInputColorInfo())) { if (input.getInputColorInfo() == null || !newInputColorInfo.equals(input.getInputColorInfo())) {
input.setSamplingGlShaderProgram( input.setSamplingGlShaderProgram(
createSamplingShaderProgram(newInputFrameInfo.colorInfo, newInputType)); createSamplingShaderProgram(newInputColorInfo, newInputType));
input.setInputColorInfo(newInputFrameInfo.colorInfo); input.setInputColorInfo(newInputColorInfo);
} }
input.setChainingListener( input.setChainingListener(
new GatedChainingListenerWrapper( new GatedChainingListenerWrapper(

View file

@ -41,7 +41,7 @@ import androidx.media3.common.C;
import androidx.media3.common.ColorInfo; import androidx.media3.common.ColorInfo;
import androidx.media3.common.DebugViewProvider; import androidx.media3.common.DebugViewProvider;
import androidx.media3.common.Effect; import androidx.media3.common.Effect;
import androidx.media3.common.FrameInfo; import androidx.media3.common.Format;
import androidx.media3.common.GlObjectsProvider; import androidx.media3.common.GlObjectsProvider;
import androidx.media3.common.GlTextureInfo; import androidx.media3.common.GlTextureInfo;
import androidx.media3.common.SurfaceInfo; import androidx.media3.common.SurfaceInfo;
@ -165,8 +165,8 @@ public abstract class MultipleInputVideoGraph implements VideoGraph {
@Override @Override
public void onInputStreamRegistered( public void onInputStreamRegistered(
@VideoFrameProcessor.InputType int inputType, @VideoFrameProcessor.InputType int inputType,
List<Effect> effects, Format format,
FrameInfo frameInfo) { List<Effect> effects) {
compositionVideoFrameProcessorInputStreamRegistrationCompleted = true; compositionVideoFrameProcessorInputStreamRegistrationCompleted = true;
queueCompositionOutputInternal(); queueCompositionOutputInternal();
} }
@ -249,7 +249,6 @@ public abstract class MultipleInputVideoGraph implements VideoGraph {
listenerExecutor, listenerExecutor,
new VideoFrameProcessor.Listener() { new VideoFrameProcessor.Listener() {
// All of this listener's methods are called on the sharedExecutorService. // All of this listener's methods are called on the sharedExecutorService.
@Override @Override
public void onError(VideoFrameProcessingException exception) { public void onError(VideoFrameProcessingException exception) {
handleVideoFrameProcessingException(exception); handleVideoFrameProcessingException(exception);
@ -367,12 +366,16 @@ public abstract class MultipleInputVideoGraph implements VideoGraph {
checkNotNull(compositionVideoFrameProcessor) checkNotNull(compositionVideoFrameProcessor)
.registerInputStream( .registerInputStream(
INPUT_TYPE_TEXTURE_ID, INPUT_TYPE_TEXTURE_ID,
compositionEffects,
// Pre-processing VideoFrameProcessors have converted the inputColor to outputColor // Pre-processing VideoFrameProcessors have converted the inputColor to outputColor
// already, so use outputColorInfo for the input color to the // already, so use outputColorInfo for the input color to the
// compositionVideoFrameProcessor. // compositionVideoFrameProcessor.
new FrameInfo.Builder(outputColorInfo, outputTexture.width, outputTexture.height) new Format.Builder()
.build()); .setColorInfo(outputColorInfo)
.setWidth(outputTexture.width)
.setHeight(outputTexture.height)
.build(),
compositionEffects,
/* offsetToAddUs= */ 0);
compositionVideoFrameProcessorInputStreamRegistered = true; compositionVideoFrameProcessorInputStreamRegistered = true;
// Return as the VideoFrameProcessor rejects input textures until the input is registered. // Return as the VideoFrameProcessor rejects input textures until the input is registered.
return; return;

View file

@ -87,16 +87,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
inputTexId, inputTexId,
/* fboId= */ C.INDEX_UNSET, /* fboId= */ C.INDEX_UNSET,
/* rboId= */ C.INDEX_UNSET, /* rboId= */ C.INDEX_UNSET,
frameInfo.width, frameInfo.format.width,
frameInfo.height); frameInfo.format.height);
checkNotNull(frameConsumptionManager).queueInputFrame(inputTexture, presentationTimeUs); checkNotNull(frameConsumptionManager).queueInputFrame(inputTexture, presentationTimeUs);
DebugTraceUtil.logEvent( DebugTraceUtil.logEvent(
COMPONENT_VFP, COMPONENT_VFP,
EVENT_QUEUE_TEXTURE, EVENT_QUEUE_TEXTURE,
presentationTimeUs, presentationTimeUs,
/* extraFormat= */ "%dx%d", /* extraFormat= */ "%dx%d",
/* extraArgs...= */ frameInfo.width, /* extraArgs...= */ frameInfo.format.width,
frameInfo.height); frameInfo.format.height);
}); });
} }

View file

@ -22,6 +22,7 @@ import android.graphics.SurfaceTexture;
import android.view.Surface; import android.view.Surface;
import androidx.annotation.GuardedBy; import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.Format;
import androidx.media3.common.FrameInfo; import androidx.media3.common.FrameInfo;
import androidx.media3.common.OnInputFrameProcessedListener; import androidx.media3.common.OnInputFrameProcessedListener;
import androidx.media3.common.VideoFrameProcessingException; import androidx.media3.common.VideoFrameProcessingException;
@ -111,7 +112,7 @@ import androidx.media3.common.util.TimestampIterator;
* frames to be registered, it may use the {@link FrameInfo} passed to {@link * frames to be registered, it may use the {@link FrameInfo} passed to {@link
* #registerInputFrame(FrameInfo)} instead of the one passed here. * #registerInputFrame(FrameInfo)} instead of the one passed here.
* *
* <p>Pixels are expanded using the {@link FrameInfo#pixelWidthHeightRatio} so that the output * <p>Pixels are expanded using the {@link Format#pixelWidthHeightRatio} so that the output
* frames' pixels have a ratio of 1. * frames' pixels have a ratio of 1.
* *
* @param inputFrameInfo Information about the next input frame. * @param inputFrameInfo Information about the next input frame.

View file

@ -36,7 +36,6 @@ import androidx.media3.common.ColorInfo;
import androidx.media3.common.DebugViewProvider; import androidx.media3.common.DebugViewProvider;
import androidx.media3.common.Effect; import androidx.media3.common.Effect;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.FrameInfo;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.PreviewingVideoGraph; import androidx.media3.common.PreviewingVideoGraph;
import androidx.media3.common.SurfaceInfo; import androidx.media3.common.SurfaceInfo;
@ -859,16 +858,13 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
ArrayList<Effect> effects = new ArrayList<>(videoEffects); ArrayList<Effect> effects = new ArrayList<>(videoEffects);
Format inputFormat = checkNotNull(this.inputFormat); Format inputFormat = checkNotNull(this.inputFormat);
inputFormat =
inputFormat
.buildUpon()
.setColorInfo(getAdjustedInputColorInfo(inputFormat.colorInfo))
.build();
checkStateNotNull(videoFrameProcessor) checkStateNotNull(videoFrameProcessor)
.registerInputStream( .registerInputStream(inputType, inputFormat, effects, /* offsetToAddUs= */ 0);
inputType,
effects,
new FrameInfo.Builder(
getAdjustedInputColorInfo(inputFormat.colorInfo),
inputFormat.width,
inputFormat.height)
.setPixelWidthHeightRatio(inputFormat.pixelWidthHeightRatio)
.build());
finalBufferPresentationTimeUs = C.TIME_UNSET; finalBufferPresentationTimeUs = C.TIME_UNSET;
} }

View file

@ -38,7 +38,7 @@ import androidx.annotation.Nullable;
import androidx.media3.common.ColorInfo; import androidx.media3.common.ColorInfo;
import androidx.media3.common.DebugViewProvider; import androidx.media3.common.DebugViewProvider;
import androidx.media3.common.Effect; import androidx.media3.common.Effect;
import androidx.media3.common.FrameInfo; import androidx.media3.common.Format;
import androidx.media3.common.GlTextureInfo; import androidx.media3.common.GlTextureInfo;
import androidx.media3.common.SurfaceInfo; import androidx.media3.common.SurfaceInfo;
import androidx.media3.common.VideoFrameProcessingException; import androidx.media3.common.VideoFrameProcessingException;
@ -283,8 +283,8 @@ public final class VideoFrameProcessorTestRunner {
@Override @Override
public void onInputStreamRegistered( public void onInputStreamRegistered(
@VideoFrameProcessor.InputType int inputType, @VideoFrameProcessor.InputType int inputType,
List<Effect> effects, Format format,
FrameInfo frameInfo) { List<Effect> effects) {
videoFrameProcessorReadyCondition.open(); videoFrameProcessorReadyCondition.open();
} }
@ -338,13 +338,14 @@ public final class VideoFrameProcessorTestRunner {
@Nullable ColorInfo colorInfo = MediaFormatUtil.getColorInfo(mediaFormat); @Nullable ColorInfo colorInfo = MediaFormatUtil.getColorInfo(mediaFormat);
videoFrameProcessor.registerInputStream( videoFrameProcessor.registerInputStream(
INPUT_TYPE_SURFACE, INPUT_TYPE_SURFACE,
effects, new Format.Builder()
new FrameInfo.Builder( .setColorInfo(colorInfo == null ? ColorInfo.SDR_BT709_LIMITED : colorInfo)
colorInfo == null ? ColorInfo.SDR_BT709_LIMITED : colorInfo, .setWidth(mediaFormat.getInteger(MediaFormat.KEY_WIDTH))
mediaFormat.getInteger(MediaFormat.KEY_WIDTH), .setHeight(mediaFormat.getInteger(MediaFormat.KEY_HEIGHT))
mediaFormat.getInteger(MediaFormat.KEY_HEIGHT))
.setPixelWidthHeightRatio(pixelWidthHeightRatio) .setPixelWidthHeightRatio(pixelWidthHeightRatio)
.build()); .build(),
effects,
/* offsetToAddUs= */ 0);
try { try {
awaitVideoFrameProcessorReady(); awaitVideoFrameProcessorReady();
} catch (VideoFrameProcessingException e) { } catch (VideoFrameProcessingException e) {
@ -374,11 +375,14 @@ public final class VideoFrameProcessorTestRunner {
videoFrameProcessorReadyCondition.close(); videoFrameProcessorReadyCondition.close();
videoFrameProcessor.registerInputStream( videoFrameProcessor.registerInputStream(
INPUT_TYPE_BITMAP, INPUT_TYPE_BITMAP,
effects, new Format.Builder()
new FrameInfo.Builder(colorInfo, inputBitmap.getWidth(), inputBitmap.getHeight()) .setColorInfo(colorInfo)
.setWidth(inputBitmap.getWidth())
.setHeight(inputBitmap.getHeight())
.setPixelWidthHeightRatio(pixelWidthHeightRatio) .setPixelWidthHeightRatio(pixelWidthHeightRatio)
.setOffsetToAddUs(offsetToAddUs) .build(),
.build()); effects,
offsetToAddUs);
awaitVideoFrameProcessorReady(); awaitVideoFrameProcessorReady();
checkState( checkState(
videoFrameProcessor.queueInputBitmap( videoFrameProcessor.queueInputBitmap(
@ -396,10 +400,14 @@ public final class VideoFrameProcessorTestRunner {
videoFrameProcessorReadyCondition.close(); videoFrameProcessorReadyCondition.close();
videoFrameProcessor.registerInputStream( videoFrameProcessor.registerInputStream(
INPUT_TYPE_BITMAP, INPUT_TYPE_BITMAP,
effects, new Format.Builder()
new FrameInfo.Builder(colorInfo, width, height) .setColorInfo(colorInfo)
.setWidth(width)
.setHeight(height)
.setPixelWidthHeightRatio(pixelWidthHeightRatio) .setPixelWidthHeightRatio(pixelWidthHeightRatio)
.build()); .build(),
effects,
/* offsetToAddUs= */ 0);
awaitVideoFrameProcessorReady(); awaitVideoFrameProcessorReady();
for (Pair<Bitmap, TimestampIterator> frame : frames) { for (Pair<Bitmap, TimestampIterator> frame : frames) {
videoFrameProcessor.queueInputBitmap(frame.first, frame.second); videoFrameProcessor.queueInputBitmap(frame.first, frame.second);
@ -410,10 +418,14 @@ public final class VideoFrameProcessorTestRunner {
throws VideoFrameProcessingException { throws VideoFrameProcessingException {
videoFrameProcessor.registerInputStream( videoFrameProcessor.registerInputStream(
INPUT_TYPE_TEXTURE_ID, INPUT_TYPE_TEXTURE_ID,
effects, new Format.Builder()
new FrameInfo.Builder(colorInfo, inputTexture.width, inputTexture.height) .setColorInfo(colorInfo)
.setWidth(inputTexture.width)
.setHeight(inputTexture.height)
.setPixelWidthHeightRatio(pixelWidthHeightRatio) .setPixelWidthHeightRatio(pixelWidthHeightRatio)
.build()); .build(),
effects,
/* offsetToAddUs= */ 0);
videoFrameProcessor.setOnInputFrameProcessedListener( videoFrameProcessor.setOnInputFrameProcessedListener(
(texId, syncObject) -> { (texId, syncObject) -> {
try { try {

View file

@ -27,12 +27,10 @@ import android.view.Surface;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.Effect; import androidx.media3.common.Effect;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.FrameInfo;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.OnInputFrameProcessedListener; import androidx.media3.common.OnInputFrameProcessedListener;
import androidx.media3.common.VideoFrameProcessor; import androidx.media3.common.VideoFrameProcessor;
import androidx.media3.common.util.Size;
import androidx.media3.common.util.TimestampIterator; import androidx.media3.common.util.TimestampIterator;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.List; import java.util.List;
@ -65,7 +63,7 @@ import java.util.concurrent.atomic.AtomicLong;
boolean isSurfaceAssetLoaderMediaItem = isMediaItemForSurfaceAssetLoader(editedMediaItem); boolean isSurfaceAssetLoaderMediaItem = isMediaItemForSurfaceAssetLoader(editedMediaItem);
durationUs = editedMediaItem.getDurationAfterEffectsApplied(durationUs); durationUs = editedMediaItem.getDurationAfterEffectsApplied(durationUs);
if (decodedFormat != null) { if (decodedFormat != null) {
Size decodedSize = getDecodedSize(decodedFormat); decodedFormat = applyDecoderRotation(decodedFormat);
ImmutableList<Effect> combinedEffects = ImmutableList<Effect> combinedEffects =
new ImmutableList.Builder<Effect>() new ImmutableList.Builder<Effect>()
.addAll(editedMediaItem.effects.videoEffects) .addAll(editedMediaItem.effects.videoEffects)
@ -75,14 +73,9 @@ import java.util.concurrent.atomic.AtomicLong;
isSurfaceAssetLoaderMediaItem isSurfaceAssetLoaderMediaItem
? VideoFrameProcessor.INPUT_TYPE_SURFACE_AUTOMATIC_FRAME_REGISTRATION ? VideoFrameProcessor.INPUT_TYPE_SURFACE_AUTOMATIC_FRAME_REGISTRATION
: getInputTypeForMimeType(checkNotNull(decodedFormat.sampleMimeType)), : getInputTypeForMimeType(checkNotNull(decodedFormat.sampleMimeType)),
decodedFormat,
combinedEffects, combinedEffects,
new FrameInfo.Builder( /* offsetToAddUs= */ initialTimestampOffsetUs + mediaItemOffsetUs.get());
checkNotNull(decodedFormat.colorInfo),
decodedSize.getWidth(),
decodedSize.getHeight())
.setPixelWidthHeightRatio(decodedFormat.pixelWidthHeightRatio)
.setOffsetToAddUs(initialTimestampOffsetUs + mediaItemOffsetUs.get())
.build());
} }
mediaItemOffsetUs.addAndGet(durationUs); mediaItemOffsetUs.addAndGet(durationUs);
} }
@ -136,11 +129,17 @@ import java.util.concurrent.atomic.AtomicLong;
videoFrameProcessor.release(); videoFrameProcessor.release();
} }
private static Size getDecodedSize(Format format) { private static Format applyDecoderRotation(Format format) {
// The decoder rotates encoded frames for display by firstInputFormat.rotationDegrees. // The decoder rotates encoded frames for display by format.rotationDegrees.
int decodedWidth = (format.rotationDegrees % 180 == 0) ? format.width : format.height; if (format.rotationDegrees % 180 == 0) {
int decodedHeight = (format.rotationDegrees % 180 == 0) ? format.height : format.width; return format;
return new Size(decodedWidth, decodedHeight); }
return format
.buildUpon()
.setWidth(format.height)
.setHeight(format.width)
.setRotationDegrees(0)
.build();
} }
private static @VideoFrameProcessor.InputType int getInputTypeForMimeType(String sampleMimeType) { private static @VideoFrameProcessor.InputType int getInputTypeForMimeType(String sampleMimeType) {