Creare un'app di editing video di base utilizzando Media3 Transformer

Le API Transformer di Jetpack Media3 sono progettate per rendere l'editing dei contenuti multimediali efficiente e affidabile. Transformer supporta una serie di operazioni, tra cui:

  • Modifica di un video con ritaglio, ridimensionamento e rotazione
  • Aggiunta di effetti come overlay e filtri
  • Elaborazione di formati speciali come video HDR e in slow motion
  • Esportazione di un elemento multimediale dopo l'applicazione delle modifiche

Questa pagina illustra alcuni dei principali casi d'uso coperti da Transformer. Per maggiori dettagli, puoi consultare le nostre guide complete su Media3 Transformer.

Inizia

Per iniziare, aggiungi una dipendenza ai moduli Transformer, Effect e Common di Jetpack Media3:

Kotlin

implementation("androidx.media3:media3-transformer:1.11.1")
implementation("androidx.media3:media3-effect:1.11.1")
implementation("androidx.media3:media3-common:1.11.1")

Alla moda

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

Assicurati di sostituire 1.11.1 con la versione della libreria che preferisci. Puoi consultare le note di rilascio per visualizzare l'ultima versione.

Classi importanti

Classe Finalità
Transformer Avviare e interrompere le trasformazioni e controllare gli aggiornamenti sull'avanzamento di una trasformazione in esecuzione.
EditedMediaItem Rappresenta un elemento multimediale da elaborare e le modifiche da applicare.
Effects Una raccolta di effetti audio e video.

Configura l'output

Con Transformer.Builder, ora puoi specificare videoMimeType e audioMimetype direttamente impostando la funzione senza dover creare un oggetto TransformationRequest.

Transcodifica tra formati

Il seguente codice mostra come configurare un oggetto Transformer per l'output di video H.265/AVC e audio 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();

Se il formato multimediale di input corrisponde già alla richiesta di trasformazione per l'audio o il video, Transformer passa automaticamente al transmuxing, ovvero alla copia dei sample compressi dal container di input al container di output senza modifiche. In questo modo si evitano i costi di calcolo e la potenziale perdita di qualità della decodifica e della ricodifica nello stesso formato.

Imposta la modalità HDR

Se il file multimediale di input è in un formato HDR, puoi scegliere tra diverse modalità di elaborazione delle informazioni HDR da parte di Transformer. Probabilmente vorrai utilizzare HDR_MODE_KEEP_HDR o HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL.

HDR_MODE_KEEP_HDR HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL
Descrizione Conservare i dati HDR, il che significa che il formato di output HDR è lo stesso del formato di input HDR. Esegui il tone mapping dell'input HDR in SDR utilizzando un tone mapper OpenGL, il che significa che il formato di output sarà in SDR.
Assistenza Supportato sui livelli API 31 e versioni successive per i dispositivi che includono un codificatore con la funzionalità FEATURE_HdrEditing. Supportato sui livelli API 29 e versioni successive.
Errori Se non è supportato, tenta di utilizzare HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL. Se non è supportato, genera un ExportException.

Sui dispositivi che supportano le funzionalità di codifica richieste e che eseguono Android 13 (livello API 33) o versioni successive, gli oggetti Transformer ti consentono di modificare i video HDR. HDR_MODE_KEEP_HDR è la modalità predefinita durante la creazione dell'oggetto Composition, come mostrato nel seguente codice:

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

Prepara un elemento multimediale

Un MediaItem rappresenta un elemento audio o video nella tua app. Un EditedMediaItem raccoglie un MediaItem insieme alle trasformazioni da applicare.

Taglia un video

Per rimuovere le parti indesiderate di un video, puoi impostare posizioni di inizio e fine personalizzate aggiungendo un ClippingConfiguration a 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();

Utilizza gli effetti integrati

Media3 include una serie di effetti video integrati per le trasformazioni comuni, ad esempio:

Classe Effetto
Presentation Scala l'elemento multimediale in base alla risoluzione o alle proporzioni
ScaleAndRotateTransformation Scala l'elemento multimediale di un moltiplicatore e/o ruotalo
Crop Ritaglia l'elemento multimediale in un frame più piccolo o più grande
OverlayEffect Aggiungi un overlay di testo o immagine sopra l'elemento multimediale

Per gli effetti audio, puoi aggiungere una sequenza di istanze AudioProcessor che trasformeranno i dati audio non elaborati (PCM). Ad esempio, puoi utilizzare un ChannelMixingAudioProcessor per mixare e scalare i canali audio.

Per utilizzare questi effetti, crea un'istanza dell'effetto o del processore audio, crea un'istanza di Effects con gli effetti audio e video che vuoi applicare all'elemento multimediale, quindi aggiungi l'oggetto Effects a un 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();

Crea effetti personalizzati

Estendendo gli effetti inclusi in Media3, puoi creare effetti personalizzati specifici per i tuoi casi d'uso. Nell'esempio seguente, utilizza la sottoclasse MatrixTransformation per aumentare lo zoom del video in modo che riempia il frame nel primo secondo di riproduzione:

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

Per personalizzare ulteriormente il comportamento di un effetto, implementa un GlShaderProgram. Il metodo queueInputFrame() viene utilizzato per elaborare i frame di input. Ad esempio, per sfruttare le funzionalità di machine learning di MediaPipe, puoi utilizzare un FrameProcessor MediaPipe per inviare ogni frame tramite un grafico MediaPipe. Guarda un esempio nell'app demo Transformer.

Visualizza l'anteprima degli effetti

Con ExoPlayer, puoi visualizzare l'anteprima degli effetti aggiunti a un elemento multimediale prima di avviare il processo di esportazione. Utilizzando gli stessi oggetti Effect di EditedMediaItem, chiama setVideoEffects() nell'istanza di ExoPlayer.

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

Puoi anche visualizzare l'anteprima degli effetti audio con ExoPlayer. Quando crei l'istanza ExoPlayer, passa un RenderersFactory personalizzato che configura i renderer audio del player in modo che l'audio venga trasmesso a un AudioSink che utilizza la sequenza AudioProcessor. Nell'esempio seguente, eseguiamo questa operazione eseguendo l'override del metodo buildAudioSink() di un DefaultRenderersFactory.

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

Avvia una trasformazione

Infine, crea un Transformer per applicare le modifiche e iniziare a esportare l'elemento multimediale risultante.

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

Se necessario, puoi annullare il processo di esportazione in modo simile con Transformer.cancel().

Controlla gli aggiornamenti sullo stato di avanzamento

Transformer.start restituisce immediatamente il risultato e viene eseguito in modo asincrono. Per eseguire una query sullo stato di avanzamento attuale di una trasformazione, chiama Transformer.getProgress(). Questo metodo accetta un ProgressHolder e, se lo stato di avanzamento è disponibile, ovvero se il metodo restituisce PROGRESS_STATE_AVAILABLE, il ProgressHolder fornito verrà aggiornato con la percentuale di avanzamento corrente.

Puoi anche collegare un listener a Transformer per ricevere notifiche sugli eventi di completamento o di errore.