Einfache Videobearbeitungsanwendung mit Media3 Transformer erstellen

Die Transformer APIs in Jetpack Media3 wurden für eine leistungsstarke und zuverlässige Medienbearbeitung entwickelt. Transformer unterstützt eine Reihe von Vorgängen, darunter:

  • Videos durch Zuschneiden, Skalieren und Drehen bearbeiten
  • Effekte wie Overlays und Filter hinzufügen
  • Verarbeitung von Spezialformaten wie HDR und Zeitlupe
  • Exportieren eines Medienelements nach dem Anwenden von Änderungen

Auf dieser Seite werden Sie durch einige der wichtigsten Anwendungsfälle von Transformer geführt. Weitere Informationen finden Sie in unseren vollständigen Leitfäden zu Media3 Transformer.

Erste Schritte

Fügen Sie zunächst eine Abhängigkeit von den Modulen Transformer, Effect und allgemeinen Jetpack Media3-Modulen hinzu:

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

Ersetzen Sie 1.3.1 durch Ihre bevorzugte Version der Bibliothek. Die neueste Version finden Sie in den Versionshinweisen.

Wichtige Klassen

Klasse Zweck
Transformer Starten und stoppen Sie Transformationen und prüfen Sie, ob Updates zum Fortschritt einer laufenden Transformation vorliegen.
EditedMediaItem Stellt ein zu verarbeitendes Medienelement und die Änderungen dar, die darauf angewendet werden sollen.
Effects Eine Sammlung von Audio- und Videoeffekten.

Ausgabe konfigurieren

Mit Transformer.Builder können Sie jetzt das Verzeichnis videoMimeType und audioMimetype angeben, indem Sie die Funktion festlegen, ohne ein TransformationRequest-Objekt erstellen zu müssen.

Zwischen Formaten transcodieren

Der folgende Code zeigt, wie ein Transformer-Objekt für die Ausgabe von H.265/AVC-Video und AAC-Audio konfiguriert wird:

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

Wenn das Eingabemedienformat bereits mit der Transformationsanfrage für Audio oder Video übereinstimmt, wechselt Transformer automatisch zu Transmuxing, d. h., die komprimierten Samples werden unverändert aus dem Eingabecontainer in den Ausgabecontainer kopiert. Dadurch werden die Rechenkosten und der potenzielle Qualitätsverlust bei der Decodierung und Neucodierung im selben Format vermieden.

HDR-Modus festlegen

Wenn die Eingabemediendatei ein HDR-Format hat, können Sie zwischen verschiedenen Modi für die Verarbeitung der HDR-Informationen durch Transformer wählen. Sie sollten entweder HDR_MODE_KEEP_HDR oder HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL verwenden.

HDR_MODE_KEEP_HDR HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL
Beschreibung Behalten Sie die HDR-Daten bei. Das HDR-Ausgabeformat entspricht also dem HDR-Eingabeformat. Die Tonemap-HDR-Eingabe für SDR über einen OpenGL Tone-Mapper, d. h. das Ausgabeformat erfolgt im SDR.
Support Wird auf API-Level 31 und höher für Geräte unterstützt, die einen Encoder mit der FEATURE_HdrEditing-Funktion enthalten. Wird auf API-Levels 29 und höher unterstützt.
Fehler Wenn die Funktion nicht unterstützt wird, wird versucht, stattdessen HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL zu verwenden. Wenn diese Funktion nicht unterstützt wird, wird ein ExportException ausgelöst.

Auf Geräten, die die erforderlichen Codierungsfunktionen unterstützen und Android 13 (API-Level 33) oder höher ausführen, kannst du mit Transformer-Objekten HDR-Videos bearbeiten. HDR_MODE_KEEP_HDR ist der Standardmodus beim Erstellen des Composition-Objekts, wie im folgenden Code dargestellt:

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

Medienelement vorbereiten

Ein MediaItem steht für ein Audio- oder Videoelement in Ihrer App. Ein EditedMediaItem erfasst ein MediaItem zusammen mit den Transformationen, die darauf angewendet werden sollen.

Video zuschneiden

Wenn du unerwünschte Teile eines Videos entfernen möchtest, kannst du benutzerdefinierte Start- und Endpositionen festlegen, indem du eine ClippingConfiguration zum MediaItem hinzufügst.

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

Integrierte Effekte verwenden

Media3 bietet eine Reihe von integrierten Videoeffekten für gängige Transformationen, z. B.:

Klasse Effekt
Presentation Medienelement nach Auflösung oder Seitenverhältnis skalieren
ScaleAndRotateTransformation Skalieren Sie das Medienelement um einen Multiplikator und/oder drehen Sie es.
Crop Medienelement auf einen kleineren oder größeren Frame zuschneiden
OverlayEffect Fügen Sie dem Medienelement ein Text- oder Bild-Overlay hinzu.

Für Audioeffekte können Sie eine Abfolge von AudioProcessor-Instanzen hinzufügen, die die PCM-Audiodaten (Rohdaten) transformieren. Sie können beispielsweise ChannelMixingAudioProcessor verwenden, um Audiokanäle zu mischen und zu skalieren.

Um diese Effekte zu verwenden, erstellen Sie eine Instanz des Effekts oder Audioprozessors, erstellen Sie eine Instanz von Effects mit den Audio- und Videoeffekten, die Sie auf das Medienelement anwenden möchten, und fügen Sie dann das Effects-Objekt einem EditedMediaItem hinzu.

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

Benutzerdefinierte Effekte erstellen

Wenn Sie die in Media3 enthaltenen Effekte erweitern, können Sie benutzerdefinierte Effekte erstellen, die für Ihre Anwendungsfälle spezifisch sind. Verwenden Sie im folgenden Beispiel die abgeleitete Klasse MatrixTransformation, um das Video so zu vergrößern, dass es den Frame nach der ersten Wiedergabesekunde ausfüllt:

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

Implementieren Sie GlShaderProgram, um das Verhalten eines Effekts weiter anzupassen. Die Methode queueInputFrame() wird zum Verarbeiten von Eingabeframes verwendet. Wenn Sie beispielsweise die ML-Funktionen von MediaPipe nutzen möchten, können Sie mit einer MediaPipe FrameProcessor jeden Frame über eine MediaPipe-Grafik senden. Ein Beispiel hierfür finden Sie in der Transformer-Demo-App.

Effekte in der Vorschau ansehen

Mit ExoPlayer können Sie sich eine Vorschau der Effekte ansehen, die einem Medienelement hinzugefügt wurden, bevor Sie den Exportvorgang starten. Verwenden Sie dasselbe Effects-Objekt wie für EditedMediaItem und rufen Sie setVideoEffects() in der ExoPlayer-Instanz auf.

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

Sie können Audioeffekte auch mit ExoPlayer in der Vorschau ansehen. Übergeben Sie beim Erstellen Ihrer ExoPlayer-Instanz ein benutzerdefiniertes RenderersFactory-Element, mit dem die Audio-Renderer des Players so konfiguriert werden, dass Audioinhalte an ein AudioSink-Element ausgegeben werden, in dem Ihre AudioProcessor-Sequenz verwendet wird. Im folgenden Beispiel wird die Methode buildAudioSink() einer DefaultRenderersFactory überschrieben.

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

Transformation starten

Erstellen Sie abschließend eine Transformer, um Ihre Änderungen zu übernehmen und mit dem Exportieren des resultierenden Medienelements zu beginnen.

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

Bei Bedarf können Sie den Export mit Transformer.cancel() abbrechen.

Auf Updates zum Fortschritt prüfen

Transformer.start gibt sofort etwas zurück und wird asynchron ausgeführt. Rufen Sie Transformer.getProgress() auf, um den aktuellen Fortschritt einer Transformation abzufragen. Diese Methode verwendet ein ProgressHolder. Wenn der Fortschrittsstatus verfügbar ist, d. h. wenn die Methode PROGRESS_STATE_AVAILABLE zurückgibt, wird die angegebene ProgressHolder mit dem aktuellen Fortschrittsprozentsatz aktualisiert.

Sie können Ihrem Transformer auch einen Listener hinzufügen, um bei Abschluss- oder Fehlerereignissen benachrichtigt zu werden.