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

Jetpack Media3 の Transformer API は、メディア編集のパフォーマンスと信頼性を高めるように設計されています。Transformer は、次のような多くのオペレーションをサポートしています。

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

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

始める

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

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

1.3.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 は自動的に Transmuxing に切り替わります。つまり、圧縮サンプルをそのまま入力コンテナから出力コンテナにコピーします。これにより、同じ形式でのデコードと再エンコードの計算コストや品質低下の可能性を回避できます。

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 動画を編集できます。次のコードに示すように、HDR_MODE_KEEP_HDRComposition オブジェクトをビルドする際のデフォルト モードです。

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 は、アプリ内の音声アイテムまたは動画アイテムを表します。EditedMediaItemMediaItem とそれに適用する変換を収集します。

動画をカットする

動画内の不要な部分を削除するには、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 と同じ 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リスナーをアタッチして、完了イベントやエラーイベントについて通知を受け取ることもできます。