ליצור אפליקציה בסיסית לעריכת סרטונים באמצעות Media3 Transformer

ממשקי ה-API של Transformer ב-Jetpack Media3 נועדו להפוך את עריכת המדיה ליעילה ואמינה. הכלי לשינוי תומך במספר פעולות, כולל:

  • שינוי סרטון באמצעות חיתוך, שינוי גודל וסיבוב
  • הוספת אפקטים כמו שכבות-על ופילטרים
  • עיבוד פורמטים מיוחדים כמו HDR וסרטונים בהילוך איטי
  • ייצוא פריט מדיה אחרי עריכה

בדף הזה מוסבר על כמה מהתרחישים העיקריים לשימוש ב-Transformer. פרטים נוספים זמינים במדריכים המלאים שלנו בנושא Media3 Transformer.

שנתחיל?

כדי להתחיל, מוסיפים תלות במודולים Transformer,‏ Effect ו-Common של 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"

חשוב להחליף את 1.7.1 בגרסה המועדפת של הספרייה. אפשר לעיין בהערות לגבי הגרסה כדי לראות את הגרסה האחרונה.

שיעורים חשובים

דרגה מטרה
Transformer להתחיל ולהפסיק טרנספורמציות ולבדוק את עדכוני ההתקדמות של טרנספורמציה שפועלת.
EditedMediaItem מייצג פריט מדיה לעיבוד ואת העריכות שיש להחיל עליו.
Effects אוסף של אפקטים לאודיו ולסרטונים.

הגדרת הפלט

עם Transformer.Builder, עכשיו אפשר לציין videoMimeType וספרייה audioMimetype על ידי הגדרת הפונקציה בלי ליצור אובייקט TransformationRequest.

המרת קידוד בין פורמטים

בדוגמת הקוד הבאה מוצג איך להגדיר אובייקט Transformer כדי להפיק וידאו בפורמט H.265/AVC ואודיו בפורמט 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();

אם פורמט המדיה של הקלט כבר תואם לבקשת השינוי של אודיו או וידאו, Transformer עובר אוטומטית להמרת פורמט, כלומר, הוא מעתיק את הדגימות הדחוסות מהקונטיינר של הקלט לקונטיינר של הפלט ללא שינוי. כך נמנעת עלות החישוב ואובדן האיכות הפוטנציאלי של פענוח וקידוד מחדש באותו פורמט.

הגדרת מצב HDR

אם קובץ המדיה של הקלט הוא בפורמט HDR, אפשר לבחור בין כמה מצבים שונים לעיבוד מידע ה-HDR על ידי Transformer. כדאי להשתמש ב-HDR_MODE_KEEP_HDR או ב-HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL.

HDR_MODE_KEEP_HDR HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL
תיאור שמירה על נתוני ה-HDR, כלומר פורמט הפלט של ה-HDR זהה לפורמט הקלט של ה-HDR. מיפוי טונים של קלט HDR ל-SDR באמצעות מיפוי טונים של OpenGL, כלומר פורמט הפלט יהיה SDR.
תמיכה נתמך ברמות API‏ 31 ומעלה במכשירים שכוללים מקודד עם יכולת FEATURE_HdrEditing. נתמך ברמות API‏ 29 ומעלה.
שגיאות אם היא לא נתמכת, המערכת תנסה להשתמש ב-HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL במקום זאת. אם הפעולה לא נתמכת, המערכת מחזירה ExportException.

במכשירים שתומכים ביכולות הנדרשות של קידוד ומריצים Android 13 ‏(API ברמה 33) ומעלה, אפשר לערוך סרטוני HDR באמצעות אובייקטים של Transformer. ‫HDR_MODE_KEEP_HDR הוא מצב ברירת המחדל כשיוצרים את האובייקט Composition, כמו שמוצג בקוד הבא:

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

הכנת קובץ מדיה

MediaItem מייצג פריט אודיו או וידאו באפליקציה. EditedMediaItem אוסף MediaItem יחד עם השינויים שצריך להחיל עליו.

חיתוך סרטון

כדי להסיר חלקים לא רצויים מסרטון, אפשר להגדיר מיקומי התחלה וסיום מותאמים אישית על ידי הוספת ClippingConfiguration ל-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();

שימוש באפקטים מובנים

‫Media3 כולל מספר אפקטים מובנים של וידאו לשינויים נפוצים, לדוגמה:

דרגה אפקט
Presentation שינוי הגודל של פריט המדיה לפי רזולוציה או יחס גובה-רוחב
ScaleAndRotateTransformation שינוי קנה המידה של פריט המדיה באמצעות מכפיל ו/או סיבוב של פריט המדיה
Crop חיתוך פריט המדיה למסגרת קטנה או גדולה יותר
OverlayEffect הוספת שכבת-על של טקסט או תמונה מעל פריט המדיה

באפקטים של אודיו, אפשר להוסיף רצף של מופעים של AudioProcessor שישנו את נתוני האודיו הגולמיים (PCM). לדוגמה, אפשר להשתמש ב-ChannelMixingAudioProcessor כדי לערבב ולשנות את הגודל של ערוצי אודיו.

כדי להשתמש באפקטים האלה, יוצרים מופע של האפקט או של מעבד האודיו, יוצרים מופע של Effects עם אפקטי האודיו והווידאו שרוצים להחיל על פריט המדיה, ואז מוסיפים את האובייקט Effects ל-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();

יצירת אפקטים בהתאמה אישית

אפשר להרחיב את האפקטים שכלולים ב-Media3 כדי ליצור אפקטים מותאמים אישית שספציפיים לתרחישי השימוש שלכם. בדוגמה הבאה, משתמשים בתת-המחלקה MatrixTransformation כדי להגדיל את הסרטון כך שימלא את המסגרת במהלך השנייה הראשונה של ההפעלה:

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

כדי להתאים אישית עוד יותר את ההתנהגות של אפקט, מטמיעים GlShaderProgram. השיטה queueInputFrame() משמשת לעיבוד של פריימים של קלט. לדוגמה, כדי להשתמש ביכולות למידת המכונה של MediaPipe, אפשר להשתמש ב-MediaPipe FrameProcessor כדי לשלוח כל פריים דרך גרף MediaPipe. דוגמה לכך אפשר לראות באפליקציית ההדגמה של Transformer.

תצוגה מקדימה של האפקטים

באמצעות ExoPlayer, אפשר לראות בתצוגה מקדימה את האפקטים שנוספו לפריט מדיה לפני שמתחילים בתהליך הייצוא. באמצעות אותו אובייקט Effects כמו ב-EditedMediaItem, קוראים ל-setVideoEffects() במופע 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();

אפשר גם לראות תצוגה מקדימה של אפקטים קוליים באמצעות ExoPlayer. כשיוצרים מופע של ExoPlayer, מעבירים RenderersFactory מותאם אישית שמגדיר את רכיבי העיבוד של האודיו בנגן כך שהאודיו יופק ל-AudioSink שמשתמש ברצף AudioProcessor שלכם. בדוגמה שלמטה, אנחנו עושים את זה על ידי החלפת השיטה buildAudioSink() של 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();

התחלת טרנספורמציה

לבסוף, יוצרים Transformer כדי להחיל את העריכות ולהתחיל לייצא את פריט המדיה שנוצר.

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

באופן דומה, אפשר לבטל את תהליך הייצוא אם צריך באמצעות Transformer.cancel().

בדיקת עדכוני התקדמות

הפונקציה Transformer.start חוזרת באופן מיידי ופועלת באופן אסינכרוני. כדי לשלוח שאילתה לגבי ההתקדמות הנוכחית של טרנספורמציה, מתקשרים אל Transformer.getProgress(). השיטה הזו מקבלת ProgressHolder, ואם מצב ההתקדמות זמין, כלומר אם השיטה מחזירה PROGRESS_STATE_AVAILABLE, אז ProgressHolder שסופק יעודכן עם אחוז ההתקדמות הנוכחי.

אפשר גם לצרף מאזין ל-Transformer כדי לקבל התראה על אירועי השלמה או שגיאה.