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

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

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

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

שנתחיל?

כדי להתחיל, צריך להוסיף תלות במודולים Transformer, Impact ו-Common של Jetpack Media3:

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

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

כיתות חשובות

דרגה המטרה
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();

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

הגדרת מצב HDR

אם קובץ המדיה והקלט הוא בפורמט HDR, אפשר לבחור מבין כמה מצבים שונים לאופן שבו טרנספורמר מעבד את המידע של ה-HDR. סביר להניח שרוצים להשתמש באחת מהאפשרויות הבאות: 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) ואילך, אובייקטים מסוג Transformer מאפשרים לערוך סרטוני HDR. 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. אם מצב ההתקדמות זמין, כלומר, אם ה-method מחזירה PROGRESS_STATE_AVAILABLE, אז הפונקציה המידע על ProgressHolder יעודכן באחוז ההתקדמות הנוכחי.

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