The Transformer APIs in Jetpack Media3 are designed to make media editing performant and reliable. Transformer supports a number of operations, including:
- Modifying a video with trimming, scaling, and rotating
- Adding effects like overlays and filters
- Processing special formats like HDR and slow-motion video
- Exporting a media item after applying edits
This page walks you through some of the key use cases covered by Transformer. For more details you can head to our full guides on Media3 Transformer.
Get started
To get started, add a dependency on the Transformer, Effect, and Common modules of Jetpack Media3:
implementation "androidx.media3:media3-transformer:1.4.1" implementation "androidx.media3:media3-effect:1.4.1" implementation "androidx.media3:media3-common:1.4.1"
Make sure to replace 1.4.1
with your preferred version of the
library. You can refer to the
release notes
to see the latest version.
Important classes
Class | Purpose |
---|---|
Transformer |
Start and stop transformations and check for progress updates on a running transformation. |
EditedMediaItem |
Represents a media item to process and the edits to apply to it. |
Effects |
A collection of audio and video effects. |
Configure the output
With Transformer.Builder
, you can now specify videoMimeType
and
audioMimetype
directory by setting the function without needing to create a
TransformationRequest
object.
Transcode between formats
The following code shows how to configure a Transformer
object to
output H.265/AVC video and AAC audio:
Kotlin
val transformer = Transformer.Builder(context) .setVideoMimeType(MimeTypes.VIDEO_H265) .setAudioMimeType(MimeTypes.AUDIO_AAC) .build()
Java
Transformer transformer = new Transformer.Builder(context) .setVideoMimeType(MimeTypes.VIDEO_H265) .setAudioMimeType(MimeTypes.AUDIO_AAC) .build();
If the input media format already matches the transformation request for audio or video, Transformer automatically switches to transmuxing, that is, copying the compressed samples from the input container to the output container without modification. This avoids the computational cost and potential quality loss of decoding and re-encoding in the same format.
Set HDR mode
If the input media file is in an HDR format, you can choose between a few
different modes for how Transformer processes the HDR information. You probably
want to use either HDR_MODE_KEEP_HDR
or
HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL
.
HDR_MODE_KEEP_HDR |
HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL |
|
---|---|---|
Description | Preserve the HDR data, meaning that the HDR output format is the same as the HDR input format. | Tonemap HDR input to SDR using an OpenGL tone-mapper, meaning that the output format will be in SDR. |
Support | Supported on API levels 31+ for devices that include an encoder with the FEATURE_HdrEditing capability. |
Supported on API levels 29+. |
Errors | If not supported, attempts to use HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL instead. |
If not supported, throws an ExportException . |
On devices that support the required encoding capabilities and run Android 13
(API level 33) or higher, Transformer
objects let you edit HDR videos.
HDR_MODE_KEEP_HDR
is the default mode when building the Composition
object,
as shown in the following code:
Kotlin
val composition = Composition.Builder( ImmutableList.of(videoSequence)) .setHdrMode(HDR_MODE_KEEP_HDR) .build()
Java
Composition composition = new Composition.Builder( ImmutableList.of(videoSequence)) .setHdrMode(Composition.HDR_MODE_KEEP_HDR) .build();
Prepare a media item
A MediaItem
represents an audio
or video item in your app. An EditedMediaItem
collects a MediaItem
along
with the transformations to apply to it.
Trim a video
To remove unwanted portions of a video, you can set custom start and end
positions by adding a ClippingConfiguration
to the MediaItem
.
Kotlin
val clippingConfiguration = MediaItem.ClippingConfiguration.Builder() .setStartPositionMs(10_000) // start at 10 seconds .setEndPositionMs(20_000) // end at 20 seconds .build() val mediaItem = MediaItem.Builder() .setUri(videoUri) .setClippingConfiguration(clippingConfiguration) .build()
Java
ClippingConfiguration clippingConfiguration = new MediaItem.ClippingConfiguration.Builder() .setStartPositionMs(10_000) // start at 10 seconds .setEndPositionMs(20_000) // end at 20 seconds .build(); MediaItem mediaItem = new MediaItem.Builder() .setUri(videoUri) .setClippingConfiguration(clippingConfiguration) .build();
Use built-in effects
Media3 includes a number of built-in video effects for common transformations, for example:
Class | Effect |
---|---|
Presentation |
Scale the media item by resolution or aspect ratio |
ScaleAndRotateTransformation |
Scale the media item by a multiplier and/or rotate the media item |
Crop |
Crop the media item to a smaller or larger frame |
OverlayEffect |
Add a text or image overlay on top of the media item |
For audio effects, you can add a sequence of
AudioProcessor
instances that will transform the raw (PCM) audio data. For example, you can use
a ChannelMixingAudioProcessor
to mix and scale audio channels.
To use these effects, create an instance of the effect or audio processor, build
an instance of Effects
with the audio and video effects you want to apply to
the media item, then add the Effects
object to an EditedMediaItem
.
Kotlin
val channelMixingProcessor = ChannelMixingAudioProcessor() val rotateEffect = ScaleAndRotateTransformation.Builder().setRotationDegrees(60f).build() val cropEffect = Crop(-0.5f, 0.5f, -0.5f, 0.5f) val effects = Effects(listOf(channelMixingProcessor), listOf(rotateEffect, cropEffect)) val editedMediaItem = EditedMediaItem.Builder(mediaItem) .setEffects(effects) .build()
Java
ChannelMixingAudioProcessor channelMixingProcessor = new ChannelMixingAudioProcessor(); ScaleAndRotateTransformation rotateEffect = new ScaleAndRotateTransformation.Builder() .setRotationDegrees(60f) .build(); Crop cropEffect = new Crop(-0.5f, 0.5f, -0.5f, 0.5f); Effects effects = new Effects( ImmutableList.of(channelMixingProcessor), ImmutableList.of(rotateEffect, cropEffect) ); EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem) .setEffects(effects) .build();
Create custom effects
By extending the effects included in Media3, you can create custom effects
specific to your use cases. In the following example, use subclass
MatrixTransformation
to zoom the video into filling the frame over the first
second of playback:
Kotlin
val zoomEffect = MatrixTransformation { presentationTimeUs -> val transformationMatrix = Matrix() // Set the scaling factor based on the playback position val scale = min(1f, presentationTimeUs / 1_000f) transformationMatrix.postScale(/* x */ scale, /* y */ scale) transformationMatrix } val editedMediaItem = EditedMediaItem.Builder(inputMediaItem) .setEffects(Effects(listOf(), listOf(zoomEffect)) .build()
Java
MatrixTransformation zoomEffect = presentationTimeUs -> { Matrix transformationMatrix = new Matrix(); // Set the scaling factor based on the playback position float scale = min(1f, presentationTimeUs / 1_000f); transformationMatrix.postScale(/* x */ scale, /* y */ scale); return transformationMatrix; }; EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(inputMediaItem) .setEffects(new Effects(ImmutableList.of(), ImmutableList.of(zoomEffect))) .build();
To further customize the behavior of an effect, implement a
GlShaderProgram
. The
queueInputFrame()
method is used to process input frames. For example, to
leverage the machine learning capabilities of
MediaPipe, you can use a
MediaPipe FrameProcessor
to send each frame through a MediaPipe graph. See an example of this in the
Transformer demo app.
Preview effects
With ExoPlayer, you can preview the effects
added to a media item before starting the export process. Using the same
Effects
object as for the EditedMediaItem
, call setVideoEffects()
on your
ExoPlayer instance.
Kotlin
val player = ExoPlayer.builder(context) .build() .also { exoPlayer -> exoPlayer.setMediaItem(inputMediaItem) exoPlayer.setVideoEffects(effects) exoPlayer.prepare() }
Java
ExoPlayer player = new ExoPlayer.builder(context).build(); player.setMediaItem(inputMediaItem); player.setVideoEffects(effects); exoPlayer.prepare();
You can also preview audio effects with ExoPlayer. When building your
ExoPlayer
instance, pass in a custom RenderersFactory
that configures the
player’s audio renderers to output audio to an AudioSink
that uses your
AudioProcessor
sequence. In the example below, we do this by overriding the
buildAudioSink()
method of a DefaultRenderersFactory
.
Kotlin
val player = ExoPlayer.Builder(context, object : DefaultRenderersFactory(context) { override fun buildAudioSink( context: Context, enableFloatOutput: Boolean, enableAudioTrackPlaybackParams: Boolean, enableOffload: Boolean ): AudioSink? { return DefaultAudioSink.Builder(context) .setEnableFloatOutput(enableFloatOutput) .setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams) .setOffloadMode(if (enableOffload) { DefaultAudioSink.OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED } else { DefaultAudioSink.OFFLOAD_MODE_DISABLED }) .setAudioProcessors(arrayOf(channelMixingProcessor)) .build() } }).build()
Java
ExoPlayer player = new ExoPlayer.Builder(context, new DefaultRenderersFactory(context) { @Nullable @Override protected AudioSink buildAudioSink( Context context, boolean enableFloatOutput, boolean enableAudioTrackPlaybackParams, boolean enableOffload ) { return new DefaultAudioSink.Builder(context) .setEnableFloatOutput(enableFloatOutput) .setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams) .setOffloadMode( enableOffload ? DefaultAudioSink.OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED : DefaultAudioSink.OFFLOAD_MODE_DISABLED) .setAudioProcessors(new AudioProcessor[]{channelMixingProcessor}) .build(); } }).build();
Start a transformation
Lastly, create a Transformer
to apply your edits and start exporting the
resulting media item.
Kotlin
val transformer = Transformer.Builder(context) .addListener(listener) .build() transformer.start(editedMediaItem, outputPath)
Java
Transformer transformer = new Transformer.Builder(context) .addListener(listener) .build(); transformer.start(editedMediaItem, outputPath);
You can similarly cancel the export process if needed with
Transformer.cancel()
.
Check for progress updates
Transformer.start
returns immediately and runs asynchronously. To query the
current progress of a transformation, call
Transformer.getProgress()
.
This method takes a ProgressHolder
, and if the progress state is available,
that is, if the method returns PROGRESS_STATE_AVAILABLE
, then the provided
ProgressHolder
will be updated with the current progress percentage.
You can also attach a
listener
to your Transformer
to be notified about completion or error events.