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:
Kotlin
implementation("androidx.media3:media3-transformer:1.10.1")
implementation("androidx.media3:media3-effect:1.10.1")
implementation("androidx.media3:media3-common:1.10.1")
Groovy
implementation "androidx.media3:media3-transformer:1.10.1"
implementation "androidx.media3:media3-effect:1.10.1"
implementation "androidx.media3:media3-common:1.10.1"
Make sure to replace 1.10.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 directly 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 Effect objects as for the
EditedMediaItem, call setVideoEffects() on your ExoPlayer instance.
Kotlin
val player = ExoPlayer.Builder(context)
.build()
.also { exoPlayer ->
exoPlayer.setMediaItem(inputMediaItem)
exoPlayer.setVideoEffects(listOf(zoomEffect))
exoPlayer.prepare()
}
Java
ExoPlayer player = new ExoPlayer.Builder(context).build();
player.setMediaItem(inputMediaItem);
player.setVideoEffects(ImmutableList.of(zoomEffect));
player.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.