Criar um app de edição de vídeo básico usando o Media3 Transformer

As APIs Transformer no Jetpack Media3 foram projetadas para tornar a edição de mídia eficiente e confiável. O Transformer oferece suporte a várias operações, incluindo:

  • Como modificar um vídeo com corte, dimensionamento e rotação
  • Adicionar efeitos, como sobreposições e filtros
  • Processamento de formatos especiais, como HDR e vídeo em câmera lenta
  • Como exportar um item de mídia após aplicar edições

Nesta página, você verá alguns dos principais casos de uso cobertos pelo Transformer. Para saber mais, acesse nossos guias completos sobre Media3 Transformer.

Primeiros passos

Para começar, adicione uma dependência aos módulos Transformer, Effect e Common do Jetpack Media3:

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

Substitua 1.4.1 pela versão de sua preferência da biblioteca. Consulte as notas da versão para conferir a versão mais recente.

Classes importantes

Classe Objetivo
Transformer Iniciar e interromper transformações e conferir atualizações de progresso em uma transformação em andamento.
EditedMediaItem Representa um item de mídia a ser processado e as edições a serem aplicadas a ele.
Effects Uma coleção de efeitos de áudio e vídeo.

Configurar a saída

Com Transformer.Builder, agora é possível especificar o diretório videoMimeType e audioMimetype definindo a função sem precisar criar um objeto TransformationRequest.

Transcodificar entre formatos

O código a seguir mostra como configurar um objeto Transformer para gerar vídeo H.265/AVC e áudio 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 o formato de mídia de entrada já corresponder à solicitação de transformação para áudio ou vídeo, o Transformer vai mudar automaticamente para transmuxing, ou seja, copiar as amostras compactadas do contêiner de entrada para o de saída sem modificação. Isso evita o custo computacional e a possível perda de qualidade da decodificação e codificação no mesmo formato.

Definir o modo HDR

Se o arquivo de mídia de entrada estiver em um formato HDR, você poderá escolher entre alguns modos diferentes de processamento das informações HDR pelo Transformer. Provavelmente, você vai querer usar HDR_MODE_KEEP_HDR ou HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL.

HDR_MODE_KEEP_HDR HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL
Descrição Preserva os dados HDR, o que significa que o formato de saída HDR é o mesmo que o formato de entrada HDR. Mapear a entrada HDR para SDR usando um mapeador de tom OpenGL, o que significa que o formato de saída será em SDR.
Suporte Suporte nos níveis 31 e mais recentes da API para dispositivos que incluem um codificador com o recurso FEATURE_HdrEditing. Tem suporte nos níveis 29 e mais recentes da API.
Erros Se não houver suporte, ele tentará usar HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL. Se não houver suporte, será gerado um ExportException.

Em dispositivos que oferecem suporte aos recursos de codificação necessários e executam o Android 13 (nível 33 da API) ou mais recente, os objetos Transformer permitem editar vídeos HDR. HDR_MODE_KEEP_HDR é o modo padrão ao criar o objeto Composition, conforme mostrado no código abaixo:

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

Preparar um item de mídia

Um MediaItem representa um item de áudio ou vídeo no app. Um EditedMediaItem coleta um MediaItem com as transformações a serem aplicadas a ele.

Cortar um vídeo

Para remover partes indesejadas de um vídeo, defina posições personalizadas de início e término adicionando um ClippingConfiguration ao 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();

Usar efeitos integrados

A Media3 inclui vários efeitos de vídeo integrados para transformações comuns, por exemplo:

Classe Efeito
Presentation Dimensionar o item de mídia por resolução ou proporção
ScaleAndRotateTransformation Dimensionar o item de mídia por um multiplicador e/ou girar o item de mídia
Crop Cortar o item de mídia para um frame menor ou maior
OverlayEffect Adicione uma sobreposição de texto ou imagem na parte superior do item de mídia

Para efeitos de áudio, é possível adicionar uma sequência de instâncias de AudioProcessor que vão transformar os dados de áudio brutos (PCM). Por exemplo, é possível usar um ChannelMixingAudioProcessor para misturar e dimensionar canais de áudio.

Para usar esses efeitos, crie uma instância do efeito ou do processador de áudio, crie uma instância de Effects com os efeitos de áudio e vídeo que você quer aplicar ao item de mídia e adicione o objeto Effects a um 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();

Criar efeitos personalizados

Ao estender os efeitos incluídos na Media3, você pode criar efeitos personalizados específicos para seus casos de uso. No exemplo abaixo, use a subclasse MatrixTransformation para aplicar zoom no vídeo e preencher o frame no primeiro segundo de reprodução:

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 ainda mais o comportamento de um efeito, implemente um GlShaderProgram. O método queueInputFrame() é usado para processar frames de entrada. Por exemplo, para aproveitar os recursos de aprendizado de máquina do MediaPipe, use um FrameProcessor do MediaPipe para enviar cada frame por um gráfico do MediaPipe. Veja um exemplo no app de demonstração do Transformer.

Visualizar efeitos

Com o ExoPlayer, você pode conferir os efeitos adicionados a um item de mídia antes de iniciar o processo de exportação. Usando o mesmo objeto Effects do EditedMediaItem, chame setVideoEffects() na instância do 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();

Também é possível conferir uma prévia dos efeitos de áudio com o ExoPlayer. Ao criar sua instância de ExoPlayer, transmita um RenderersFactory personalizado que configure os renderizadores de áudio do player para gerar áudio em um AudioSink que usa sua sequência AudioProcessor. No exemplo abaixo, fazemos isso substituindo o método buildAudioSink() de um 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();

Iniciar uma transformação

Por fim, crie um Transformer para aplicar suas edições e começar a exportar o item de mídia 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);

Também é possível cancelar o processo de exportação, se necessário, com Transformer.cancel().

Verificar atualizações de progresso

Transformer.start retorna imediatamente e é executado de forma assíncrona. Para consultar o progresso atual de uma transformação, chame Transformer.getProgress(). Esse método recebe um ProgressHolder e, se o estado de progresso estiver disponível, ou seja, se o método retornar PROGRESS_STATE_AVAILABLE, o ProgressHolder fornecido será atualizado com a porcentagem de progresso atual.

Também é possível anexar um listener ao Transformer para receber notificações sobre eventos de conclusão ou erro.