diff --git a/libraries/common/src/main/java/androidx/media3/common/FrameProcessor.java b/libraries/common/src/main/java/androidx/media3/common/FrameProcessor.java index a2a73bdef0..f7d52052e7 100644 --- a/libraries/common/src/main/java/androidx/media3/common/FrameProcessor.java +++ b/libraries/common/src/main/java/androidx/media3/common/FrameProcessor.java @@ -16,6 +16,7 @@ package androidx.media3.common; import android.content.Context; +import android.graphics.Bitmap; import android.opengl.EGLExt; import android.view.Surface; import androidx.annotation.Nullable; @@ -123,6 +124,20 @@ public interface FrameProcessor { /** Indicates the frame should be dropped after {@link #releaseOutputFrame(long)} is invoked. */ long DROP_OUTPUT_FRAME = -2; + /** + * Provides an input {@link Bitmap} to the {@link FrameProcessor}. + * + *

Can be called on any thread. + * + * @param inputBitmap The {@link Bitmap} queued to the {@link FrameProcessor}. + * @param durationUs The duration for which to display the {@code inputBitmap}, in microseconds. + * @param frameRate The frame rate at which to display the {@code inputBitmap}, in frames per + * second. + */ + // TODO(b/262693274): Remove duration & frameRate parameters when EditedMediaItem can be signalled + // down to the processors. + void queueInputBitmap(Bitmap inputBitmap, long durationUs, int frameRate); + /** * Returns the input {@link Surface}, where {@link FrameProcessor} consumes input frames from. * diff --git a/libraries/effect/src/main/java/androidx/media3/effect/GlEffectsFrameProcessor.java b/libraries/effect/src/main/java/androidx/media3/effect/GlEffectsFrameProcessor.java index b0aea12cf6..886c1f822f 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/GlEffectsFrameProcessor.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/GlEffectsFrameProcessor.java @@ -21,6 +21,7 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull; import static com.google.common.collect.Iterables.getLast; import android.content.Context; +import android.graphics.Bitmap; import android.graphics.SurfaceTexture; import android.opengl.EGLContext; import android.opengl.EGLDisplay; @@ -409,6 +410,9 @@ public final class GlEffectsFrameProcessor implements FrameProcessor { inputExternalTextureManager.getSurfaceTexture().setDefaultBufferSize(width, height); } + @Override + public void queueInputBitmap(Bitmap inputBitmap, long durationUs, int frameRate) {} + @Override public Surface getInputSurface() { return inputSurface; diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/SamplePipeline.java b/libraries/transformer/src/main/java/androidx/media3/transformer/SamplePipeline.java index 20440d51d5..9e43fd8033 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/SamplePipeline.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/SamplePipeline.java @@ -34,7 +34,7 @@ import androidx.media3.decoder.DecoderInputBuffer; private final long streamStartPositionUs; private final MuxerWrapper muxerWrapper; - private final @C.TrackType int trackType; + private final @C.TrackType int outputTrackType; private boolean muxerWrapperTrackAdded; @@ -42,7 +42,10 @@ import androidx.media3.decoder.DecoderInputBuffer; Format firstInputFormat, long streamStartPositionUs, MuxerWrapper muxerWrapper) { this.streamStartPositionUs = streamStartPositionUs; this.muxerWrapper = muxerWrapper; - trackType = MimeTypes.getTrackType(firstInputFormat.sampleMimeType); + outputTrackType = + MimeTypes.isImage(firstInputFormat.sampleMimeType) + ? C.TRACK_TYPE_VIDEO + : MimeTypes.getTrackType(firstInputFormat.sampleMimeType); } protected static TransformationException createNoSupportedMimeTypeException(Format format) { @@ -113,7 +116,7 @@ import androidx.media3.decoder.DecoderInputBuffer; } if (isMuxerInputEnded()) { - muxerWrapper.endTrack(trackType); + muxerWrapper.endTrack(outputTrackType); return false; } @@ -127,7 +130,7 @@ import androidx.media3.decoder.DecoderInputBuffer; // buffer from all samples so that they are guaranteed to start from zero in the output file. try { if (!muxerWrapper.writeSample( - trackType, + outputTrackType, checkStateNotNull(muxerInputBuffer.data), muxerInputBuffer.isKeyFrame(), samplePresentationTimeUs)) { diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSamplePipeline.java b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSamplePipeline.java index 9e7d3bb7d0..b69062dac7 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSamplePipeline.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSamplePipeline.java @@ -25,6 +25,7 @@ import static androidx.media3.transformer.TransformationRequest.HDR_MODE_TONE_MA import static androidx.media3.transformer.TransformationRequest.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL; import android.content.Context; +import android.graphics.Bitmap; import android.media.MediaCodec; import android.view.Surface; import androidx.annotation.Nullable; @@ -204,6 +205,11 @@ import org.checkerframework.dataflow.qual.Pure; new FrameInfo.Builder(firstFrameInfo).setOffsetToAddUs(mediaItemOffsetUs).build()); } + @Override + public void queueInputBitmap(Bitmap inputBitmap, long durationUs, int frameRate) { + frameProcessor.queueInputBitmap(inputBitmap, durationUs, frameRate); + } + @Override public Surface getInputSurface() { return frameProcessor.getInputSurface(); @@ -352,10 +358,15 @@ import org.checkerframework.dataflow.qual.Pure; this.transformationRequest = transformationRequest; this.fallbackListener = fallbackListener; - requestedOutputMimeType = - transformationRequest.videoMimeType != null - ? transformationRequest.videoMimeType - : checkNotNull(inputFormat.sampleMimeType); + String inputSampleMimeType = checkNotNull(inputFormat.sampleMimeType); + + if (transformationRequest.videoMimeType != null) { + requestedOutputMimeType = transformationRequest.videoMimeType; + } else if (MimeTypes.isImage(inputSampleMimeType)) { + requestedOutputMimeType = MimeTypes.VIDEO_H265; + } else { + requestedOutputMimeType = inputSampleMimeType; + } supportedEncoderNamesForHdrEditing = EncoderUtil.getSupportedEncoderNamesForHdrEditing( requestedOutputMimeType, inputFormat.colorInfo);