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 では、TransformationRequest オブジェクトを作成せずに、関数を設定することで videoMimeType ディレクトリと audioMimetype ディレクトリを指定できるようになりました。

形式間のコード変換

次のコードは、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 になります。
サポート API レベル 31 以降で、FEATURE_HdrEditing 機能を持つエンコーダを含むデバイスでサポートされます。 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 の機械学習機能を利用するには、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 インスタンスを作成するときは、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 に接続して、完了イベントまたはエラーイベントの通知を受けることもできます。