使用 Media3 Transformer 建立基本的影片編輯應用程式

Jetpack Media3 中的 Transformer API 旨在讓媒體編輯作業更有效率且更可靠。Transformer 支援多項作業,包括:

  • 透過剪輯、縮放及旋轉功能修改影片
  • 加入疊加和濾鏡等特效
  • 處理 HDR 和慢動作影片等特殊格式
  • 套用編輯內容後匯出媒體項目

本頁面將逐步說明 Transformer 涵蓋的部分重要用途。如需更多詳細資訊,請參閱 Media3 Transformer 的完整指南。

開始使用

如要開始使用,請在 Jetpack Media3 的 Transformer、Effect 和 Common 模組上新增依附元件:

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

請務必將 1.5.0 替換為您偏好的程式庫版本。您可以參閱版本資訊,查看最新版本。

重要類別

類別 目的
Transformer 啟動及停止轉換作業,並檢查正在執行的轉換作業的進度更新。
EditedMediaItem 代表要處理的媒體項目,以及要套用的編輯內容。
Effects 音訊和視訊特效集合。

設定輸出

有了 Transformer.Builder,您現在可以透過設定函式來指定 videoMimeTypeaudioMimetype 目錄,而無需建立 TransformationRequest 物件。

在格式之間轉碼

下列程式碼顯示如何設定 Transformer 物件,以便輸出 H.265/AVC 視訊和 AAC 音訊:

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

如果輸入媒體格式已符合音訊或視訊的轉換要求,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 以及要套用的轉換。

剪輯影片

如要移除影片中不需要的部分,您可以將 ClippingConfiguration 新增至 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();

使用內建特效

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,以便收到完成或錯誤事件的通知。