Cómo crear una app básica de edición de video con Media3 Transformer

Las APIs de Transformer en Jetpack Media3 están diseñadas para que la edición de medios sea confiable y tenga un buen rendimiento. Transformer admite varias operaciones, incluidas las siguientes:

  • Cómo modificar un video con las funciones de corte, ajuste y rotación
  • Agregar efectos, como superposiciones y filtros
  • Procesamiento de formatos especiales, como video en HDR y en cámara lenta
  • Cómo exportar un elemento multimedia después de aplicar ediciones

En esta página, se explican algunos de los casos de uso clave que abarca Transformer. Para obtener más detalles, consulta nuestras guías completas sobre Media3 Transformer.

Comenzar

Para comenzar, agrega una dependencia en los módulos Transformer, Effect y Common de Jetpack Media3:

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

Asegúrate de reemplazar 1.7.1 por la versión de la biblioteca que prefieras. Puedes consultar las notas de la versión para ver la versión más reciente.

Clases importantes

Clase Propósito
Transformer Iniciar y detener transformaciones, y verificar las actualizaciones de progreso de una transformación en ejecución
EditedMediaItem Representa un elemento multimedia para procesar y las ediciones que se le aplicarán.
Effects Colección de efectos de audio y video.

Configura el resultado

Con Transformer.Builder, ahora puedes especificar el directorio videoMimeType y audioMimetype configurando la función sin necesidad de crear un objeto TransformationRequest.

Transcodificación entre formatos

El siguiente código muestra cómo configurar un objeto Transformer para generar video H.265/AVC y 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();

Si el formato de los medios de entrada ya coincide con la solicitud de transformación para audio o video, Transformer cambia automáticamente a la transmuxing, es decir, copia las muestras comprimidas del contenedor de entrada al contenedor de salida sin modificaciones. Esto evita el costo de procesamiento y la posible pérdida de calidad de la decodificación y la recodificación en el mismo formato.

Cómo configurar el modo HDR

Si el archivo multimedia de entrada está en formato HDR, puedes elegir entre algunos modos diferentes para que Transformer procese la información de HDR. Probablemente quieras usar 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
Descripción Conservar los datos HDR, lo que significa que el formato de salida HDR es el mismo que el formato de entrada HDR Asigna el tono de la entrada HDR a SDR con un asignador de tonos de OpenGL, lo que significa que el formato de salida será SDR.
Asistencia Se admite en niveles de API 31 y posteriores para dispositivos que incluyen un codificador con la capacidad FEATURE_HdrEditing. Se admite en los niveles de API 29 y posteriores.
Errores Si no se admite, se intenta usar HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL en su lugar. Si no se admite, se arroja un ExportException.

En los dispositivos que admiten las capacidades de codificación requeridas y ejecutan Android 13 (nivel de API 33) o versiones posteriores, los objetos Transformer te permiten editar videos HDR. HDR_MODE_KEEP_HDR es el modo predeterminado cuando se compila el objeto Composition, como se muestra en el siguiente código:

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 multimedia

Un MediaItem representa un elemento de audio o video en tu app. Un EditedMediaItem recopila un MediaItem junto con las transformaciones que se le aplicarán.

Cómo cortar un video

Para quitar las partes no deseadas de un video, puedes establecer posiciones de inicio y finalización personalizadas agregando un ClippingConfiguration al 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();

Cómo usar los efectos integrados

Media3 incluye varios efectos de video integrados para transformaciones comunes, como los siguientes:

Clase Efecto
Presentation Ajusta la escala del elemento multimedia según la resolución o la relación de aspecto
ScaleAndRotateTransformation Ajustar la escala del elemento multimedia con un multiplicador o rotarlo
Crop Recorta el elemento multimedia a un encuadre más pequeño o más grande
OverlayEffect Agrega una superposición de texto o imagen sobre el elemento multimedia.

En el caso de los efectos de audio, puedes agregar una secuencia de instancias de AudioProcessor que transformarán los datos de audio sin procesar (PCM). Por ejemplo, puedes usar un ChannelMixingAudioProcessor para mezclar y ajustar los canales de audio.

Para usar estos efectos, crea una instancia del efecto o del procesador de audio, crea una instancia de Effects con los efectos de audio y video que quieras aplicar al elemento multimedia y, luego, agrega el objeto 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();

Cómo crear efectos personalizados

Si extiendes los efectos incluidos en Media3, puedes crear efectos personalizados específicos para tus casos de uso. En el siguiente ejemplo, usa la subclase MatrixTransformation para acercar el video y que llene el fotograma durante el primer segundo de reproducción:

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

Para personalizar aún más el comportamiento de un efecto, implementa un GlShaderProgram. El método queueInputFrame() se usa para procesar los fotogramas de entrada. Por ejemplo, para aprovechar las capacidades de aprendizaje automático de MediaPipe, puedes usar un FrameProcessor de MediaPipe para enviar cada fotograma a través de un gráfico de MediaPipe. Puedes ver un ejemplo de esto en la app de demostración de Transformer.

Obtén una vista previa de los efectos

Con ExoPlayer, puedes obtener una vista previa de los efectos agregados a un elemento multimedia antes de iniciar el proceso de exportación. Con el mismo objeto Effects que para EditedMediaItem, llama a setVideoEffects() en tu instancia de ExoPlayer.

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

También puedes obtener una vista previa de los efectos de audio con ExoPlayer. Cuando compiles tu instancia de ExoPlayer, pasa un RenderersFactory personalizado que configure los renderizadores de audio del reproductor para que envíen audio a un AudioSink que use tu secuencia de AudioProcessor. En el siguiente ejemplo, anulamos el método buildAudioSink() de un DefaultRenderersFactory para lograrlo.

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

Cómo iniciar una transformación

Por último, crea un Transformer para aplicar tus ediciones y comenzar a exportar el elemento multimedia resultante.

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

Del mismo modo, puedes cancelar el proceso de exportación si es necesario con Transformer.cancel().

Cómo consultar las actualizaciones de progreso

Transformer.start se muestra de inmediato y se ejecuta de forma asíncrona. Para consultar el progreso actual de una transformación, llama a Transformer.getProgress(). Este método toma un ProgressHolder y, si el estado de progreso está disponible, es decir, si el método devuelve PROGRESS_STATE_AVAILABLE, el ProgressHolder proporcionado se actualizará con el porcentaje de progreso actual.

También puedes adjuntar un listener a tu Transformer para recibir notificaciones sobre eventos de finalización o error.