Tworzenie podstawowej aplikacji do edycji filmów za pomocą Media3 Transformer

Interfejsy API Transformer w Jetpack Media3 zostały zaprojektowane tak, aby edytowanie multimediów było wydajne i niezawodne. Transformer obsługuje wiele operacji, w tym:

  • modyfikowanie filmu przez przycinanie, skalowanie i obracanie;
  • dodawanie efektów takich jak nakładki i filtry;
  • przetwarzanie formatów specjalnych, takich jak HDR i filmy w zwolnionym tempie;
  • Eksportowanie elementu multimedialnego po zastosowaniu zmian

Na tej stronie znajdziesz omówienie niektórych kluczowych przypadków użycia dotyczących usługi Transformer. Więcej informacji znajdziesz w pełnych przewodnikach dotyczących Media3 Transformer.

Rozpocznij

Aby rozpocząć, dodaj zależność od modułów Transformer, Effect i Common w 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"

Pamiętaj, aby zastąpić 1.4.1 preferowaną wersją biblioteki. Informacje o najnowszej wersji znajdziesz w informacjach o wersji.

Ważne klasy

Kategoria Cel
Transformer Uruchamianie i zatrzymywanie przekształceń oraz sprawdzanie postępu bieżącego przekształcania.
EditedMediaItem Reprezentuje element multimedialny do przetworzenia i wprowadzone w nim zmiany.
Effects Kolekcja efektów dźwiękowych i wideo.

Konfigurowanie danych wyjściowych

Dzięki funkcji Transformer.Builder możesz teraz określić katalog videoMimeTypeaudioMimetype, ustawiając funkcję bez konieczności tworzenia obiektu TransformationRequest.

Transkodowanie między formatami

Poniższy kod pokazuje, jak skonfigurować obiekt Transformer, aby generował wideo H.265/AVC i dźwięk 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();

Jeśli format multimediów wejściowych jest już zgodny z żądaniem przekształcenia dźwięku lub wideo, Transformer automatycznie przełącza się na transmuxowanie, czyli kopiowanie skompresowanych próbek z kontenera wejściowego do kontenera wyjściowego bez wprowadzania zmian. Pozwala to uniknąć kosztów obliczeniowych i potencjalnej utraty jakości podczas dekodowania i ponownie kodowania w tym samym formacie.

Ustawianie trybu HDR

Jeśli plik multimediów wejściowych jest w formacie HDR, możesz wybrać jeden z kilku różnych trybów przetwarzania informacji HDR przez Transformer. Prawdopodobnie chcesz użyć właściwości HDR_MODE_KEEP_HDR lub HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL.

HDR_MODE_KEEP_HDR HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL
Opis Zachowaj dane HDR, co oznacza, że format wyjściowy HDR jest taki sam jak format wejściowy HDR. Przekształcanie HDR na SDR za pomocą narzędzia do mapowania tonalnego OpenGL, co oznacza, że format wyjściowy będzie SDR.
Pomoc Obsługiwane na poziomie interfejsu API 31 lub nowszym na urządzeniach z enkoderem z opcją FEATURE_HdrEditing. Obsługiwane na poziomie API 29 i wyższych.
Błędy Jeśli nie jest obsługiwany, spróbuje użyć HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL. Jeśli nie jest obsługiwany, zgłaszany jest błąd ExportException.

Na urządzeniach z Androidem 13 (poziom interfejsu API 33) lub nowszym, które obsługują wymagane funkcje kodowania, obiekty Transformer umożliwiają edytowanie filmów HDR. Tryb HDR_MODE_KEEP_HDR jest domyślnym trybem podczas tworzenia obiektu Composition, jak pokazano w tym kodzie:

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

Przygotuj element multimedialny

Element MediaItem reprezentuje element audio lub wideo w aplikacji. Element EditedMediaItem zbiera element MediaItem wraz z zastosowaniami, które mają zostać do niego zastosowane.

Przycinanie filmu

Aby usunąć niechciane fragmenty filmu, możesz ustawić niestandardowe pozycje początkową i końcową, dodając ClippingConfiguration do 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();

Korzystanie z wbudowanych efektów

Media3 zawiera kilka wbudowanych efektów wideo do stosowania do typowych przekształceń, takich jak:

Kategoria Efekt
Presentation Skalowanie elementu multimedialnego według rozdzielczości lub formatu obrazu
ScaleAndRotateTransformation Zmień rozmiar elementu multimedialnego za pomocą mnożnika lub obróć go.
Crop Przytnij element multimedialny do mniejszego lub większego kadru
OverlayEffect Dodaj nakładkę tekstową lub obrazową na element multimedialny.

W przypadku efektów dźwiękowych możesz dodać sekwencję instancji AudioProcessor, które przekształcą surowe dane dźwiękowe (PCM). Możesz na przykład użyć ChannelMixingAudioProcessor do zmiksowania i skalowania kanałów audio.

Aby użyć tych efektów, utwórz instancję efektu lub procesora audio, zbuduj instancję Effects z efektami audio i wideo, które chcesz zastosować do elementu multimedialnego, a potem dodaj obiekt Effects do obiektu 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();

Tworzenie efektów niestandardowych

Dzięki rozszerzeniu efektów dostępnych w Media3 możesz tworzyć efekty niestandardowe dostosowane do konkretnych zastosowań. W tym przykładzie użyj klasy podrzędnej MatrixTransformation, aby powiększyć film tak, aby wypełniał kadr w ciągu pierwszej sekundy odtwarzania:

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

Aby jeszcze bardziej dostosować działanie efektu, zaimplementuj GlShaderProgram. Metoda queueInputFrame() służy do przetwarzania ramek wejściowych. Aby na przykład wykorzystać możliwości sztucznej inteligencji w MediaPipe, możesz użyć FrameProcessor MediaPipe, aby przesyłać poszczególne klatki do grafu MediaPipe. Przykład znajdziesz w aplikacji demonstracyjnej Transformer.

Podgląd efektów

Dzięki ExoPlayerowi możesz wyświetlić podgląd efektów dodanych do elementu multimedialnego przed rozpoczęciem procesu eksportowania. Używając tego samego obiektu Effects co w przypadku EditedMediaItem, wywołaj metodę setVideoEffects() w instancji ExoPlayera.

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

Efekty dźwiękowe możesz też wyświetlić za pomocą ExoPlayera. Podczas tworzenia wystąpienia ExoPlayer podaj niestandardowy RenderersFactory, który skonfiguruje przetwarzacze audio odtwarzacza w celu przesyłania dźwięku do AudioSink, który używa sekwencji AudioProcessor. W przykładzie poniżej robimy to, zastępując metodę buildAudioSink() klasy 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();

Rozpoczynanie przekształcenia

Na koniec utwórz Transformer, aby zastosować zmiany i rozpocząć eksportowanie utworzonego elementu multimedialnego.

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

W razie potrzeby możesz też anulować proces eksportowania, klikając Transformer.cancel().

Sprawdzanie postępów

Transformer.start zwraca wartość natychmiast i działa asynchronicznie. Aby sprawdzić bieżący postęp transformacji, wywołaj funkcję Transformer.getProgress(). Ta metoda przyjmuje parametr ProgressHolder. Jeśli stan postępu jest dostępny, czyli jeśli metoda zwraca wartość PROGRESS_STATE_AVAILABLE, podany obiekt ProgressHolder zostanie zaktualizowany o bieżący procent postępu.

Możesz też dołączyć do Transformer odbiorcę, aby otrzymywać powiadomienia o wydarzeniu zakończenia lub błędu.