Media3 Transformer を使用して基本的な動画編集アプリを作成する

Jetpack Media3 の Transformer API は、メディア編集を高性能かつ信頼性の高いものにするように設計されています。Transformer は、次のようなさまざまなオペレーションをサポートしています。

  • トリミング、スケーリング、回転による動画の変更
  • オーバーレイやフィルタなどのエフェクトの追加
  • HDR やスローモーション動画などの特殊な形式の処理
  • 編集を適用した後のメディア アイテムのエクスポート

このページでは、Transformer でカバーされている主なユースケースについて説明します。 詳細については、Media3 Transformer の完全なガイドをご覧ください。

始める

まず、Jetpack Media3 の Transformer、Effect、Common モジュールに依存関係を追加します。

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"

1.10.1 は、ライブラリの任意のバージョンに置き換えてください。最新 バージョンについては、リリースノートをご覧ください。

重要なクラス

クラス 目的
Transformer 変換の開始と停止、実行中の変換の進行状況の更新の確認を行います。
EditedMediaItem 処理するメディア アイテムと、適用する編集内容を表します。
Effects 音声エフェクトと動画エフェクトのコレクションです。

出力を構成する

Transformer.Builder を使用すると、TransformationRequest オブジェクトを作成しなくても、関数を設定して videoMimeTypeaudioMimetype を直接指定できるようになりました。

形式間でトランスコードする

次のコードは、H.265/AVC 動画と AAC 音声を出力するように Transformer オブジェクトを構成する方法を示しています。

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_HDR または HDR_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 と、それに適用する変換を収集します。

動画をトリミングする

動画の不要な部分を削除するには、MediaItemClippingConfiguration を追加して、カスタムの開始位置と終了位置を設定します。

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 メディア アイテムの上にテキストまたは画像のオーバーレイを追加する

音声エフェクトの場合は、生の(PCM)音声データを変換する AudioProcessor インスタンスのシーケンスを追加できます。たとえば、 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 を使用して、再生の最初の 1 秒間に動画を拡大してフレーム全体に表示します。

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

エフェクトの動作をさらにカスタマイズするには、GlShaderProgramを実装します。 queueInputFrame() メソッドは、入力フレームの処理に使用されます。たとえば、 MediaPipe の ML 機能を利用するには、 MediaPipe FrameProcessor を使用して各フレームを MediaPipe グラフに送信します。この例については、Transformer デモアプリをご覧ください。

エフェクトをプレビューする

ExoPlayer を使用すると、エクスポート プロセスを開始する前に 、メディア アイテムに追加されたエフェクトをプレビューできます。 EditedMediaItem と同じ Effect オブジェクトを使用して、ExoPlayer インスタンスで setVideoEffects() を呼び出します。

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

ExoPlayer で音声エフェクトをプレビューすることもできます。ExoPlayer インスタンスをビルドするときに、プレーヤーのオーディオ レンダラを構成して AudioProcessor シーケンスを使用する AudioSink に音声を出力するカスタム RenderersFactory を渡します。次の例では、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 にアタッチして、 完了イベントまたはエラーイベントの通知を受け取ることもできます。