使用 Media3 Transformer 创建基本的视频编辑应用

Jetpack Media3 中的 Transformer API 旨在实现高效可靠的媒体编辑。Transformer 支持多种操作,包括:

  • 通过剪辑、缩放和旋转修改视频
  • 添加叠加层和滤镜等特效
  • 处理特殊格式,例如 HDR 和慢动作视频
  • 在应用修改后导出媒体项

本页面介绍了 Transformer 涵盖的一些关键使用场景。如需了解详情,请参阅有关 Media3 Transformer 的完整指南。

开始使用

首先,为 Jetpack Media3 的 Transformer、Effect 和 Common 模块添加依赖项:

implementation "androidx.media3:media3-transformer:1.2.1"
implementation "androidx.media3:media3-effect:1.2.1"
implementation "androidx.media3:media3-common:1.2.1"

请务必将 1.2.1 替换为您偏好的库版本。您可以参阅版本说明,查看最新版本。

重要课程

目的
Transformer 开始和停止转换,并检查正在运行的转换的进度更新。
EditedMediaItem 表示要处理的媒体项以及要应用到该项的修改。
Effects 音频和视频效果的集合。

配置输出

借助 Transformer.Builder,您现在可以通过设置该函数来指定 videoMimeTypeaudioMimetype 目录,而无需创建 TransformationRequest 对象。

不同格式之间的转码

以下代码展示了如何配置 Transformer 对象以输出 H.265/AVC 视频和 AAC 音频:

Kotlin

val transformer = Transformer.Builder(context)
    .setVideoMimeType(MimeTypes.VIDEO_H265)
    .setAudioMimeType(AUDIO_AAC)
    .build()

Java

Transformer transformer = new Transformer.Builder(context)
    .setVideoMimeType(MimeTypes.VIDEO_H265)
    .setAudioMimeType(AUDIO_AAC)
    .build();

如果输入媒体格式已经与音频或视频的转换请求匹配,则 Transformer 会自动切换到传输多路复用,也就是说,将压缩的样本从输入容器复制到输出容器,不做任何修改。这样可以避免对同一格式进行解码和重新编码造成的计算开销和潜在质量损失。

设置 HDR 模式

如果输入媒体文件采用 HDR 格式,对于 Transformer 处理 HDR 信息的方式,您可以从几种不同的模式中进行选择。您可能需要使用 HDR_MODE_KEEP_HDRHDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL

HDR_MODE_KEEP_HDR HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL
说明 保留 HDR 数据,这意味着 HDR 输出格式与 HDR 输入格式相同。 使用 OpenGL 色调映射器将 HDR 输入色调映射至 SDR,这意味着输出格式将为 SDR。
支持 对于包含具有 FEATURE_HdrEditing 功能的编码器的设备,在 API 级别 31 及更高级别上受支持。 在 API 级别 29 及更高级别上受支持。
错误 如果不受支持,则尝试改用 HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL 如果不受支持,会抛出 ExportException

在支持所需编码功能并搭载 Android 13(API 级别 33)或更高版本的设备上,您可以使用 Transformer 对象编辑 HDR 视频。构建 Composition 对象时,HDR_MODE_KEEP_HDR 是默认模式,如以下代码所示:

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();

准备媒体内容

MediaItem 表示应用中的音频或视频项。EditedMediaItem 会收集 MediaItem 以及要对其应用的转换。

剪辑视频

如需移除视频中不需要的部分,您可以向 MediaItem 添加 ClippingConfiguration,以设置自定义开始和结束位置。

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();

使用内置效果

Media3 包含许多用于常见转换的内置视频特效,例如:

影响
Presentation 按分辨率或宽高比缩放媒体内容
ScaleAndRotateTransformation 按倍数缩放媒体项和/或旋转媒体项
Crop 将媒体项剪裁为较小或较大的帧
OverlayEffect 在媒体内容之上添加文字图片叠加层

对于音效,您可以添加一系列 AudioProcessor 实例,用于转换原始 (PCM) 音频数据。例如,您可以使用 ChannelMixingAudioProcessor 混音和缩放声道。

如需使用这些效果,请创建效果或音频处理器的实例,使用要应用于媒体项的音频和视频效果构建 Effects 实例,然后将 Effects 对象添加到 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();

创建自定义效果

通过扩展 Media3 中包含的效果,您可以根据自己的使用情形创建自定义效果。在以下示例中,请使用子类 MatrixTransformation 在播放的第一秒内缩放视频,使其填满整个帧:

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();

如需进一步自定义效果的行为,请实现 GlShaderProgramqueueInputFrame() 方法用于处理输入帧。例如,如需利用 MediaPipe 的机器学习功能,您可以使用 MediaPipe FrameProcessor 通过 MediaPipe 图发送每一帧。如需查看相关示例,请参阅 Transformer 演示应用

预览效果

借助 ExoPlayer,您可以在开始导出过程之前预览添加到媒体内容的特效。使用与 EditedMediaItem 相同的 Effects 对象,对 ExoPlayer 实例调用 setVideoEffects()

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();

您还可以使用 ExoPlayer 预览音效。构建 ExoPlayer 实例时,请传入一个自定义 RenderersFactory,用于将播放器的音频渲染程序配置为将音频输出到使用 AudioProcessor 序列的 AudioSink。在下面的示例中,我们将通过替换 DefaultRenderersFactorybuildAudioSink() 方法来完成此操作。

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();

开始转型

最后,创建一个 Transformer 以应用您的修改,并开始导出生成的媒体项。

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);

同样,如果需要,您也可以使用 Transformer.cancel() 取消导出过程。

查看进度更新

Transformer.start 会立即返回并异步运行。如需查询转换的当前进度,请调用 Transformer.getProgress()。此方法接受 ProgressHolder,如果进度状态可用,也就是说,如果该方法返回 PROGRESS_STATE_AVAILABLE,则提供的 ProgressHolder 将更新为当前进度百分比。

您还可以将监听器附加到 Transformer,以便接收完成或错误事件的通知。