إنشاء تطبيق أساسي لتعديل الفيديوهات باستخدام Media3 Transformer

تم تصميم واجهات برمجة التطبيقات Transformer في Jetpack Media3 لجعل تعديل الوسائط موثوقًا وعالي الأداء. تتيح أداة Transformer عددًا من العمليات، بما في ذلك:

  • تعديل فيديو من خلال القطع وتغيير الحجم والتدوير
  • إضافة تأثيرات، مثل التراكبات والفلاتر
  • معالجة التنسيقات الخاصة، مثل فيديو HDR والفيديو بالحركة البطيئة
  • تصدير عنصر وسائط بعد تطبيق التعديلات

توضّح هذه الصفحة بعض حالات الاستخدام الرئيسية التي تغطيها أداة Transformer. لمزيد من التفاصيل، يمكنكم الانتقال إلى الأدلة الكاملة حول أداة Media3 Transformer.

البدء

للبدء، أضيفوا اعتمادية على وحدات Transformer وEffect وCommon في Jetpack Media3:

Kotlin

implementation("androidx.media3:media3-transformer:1.11.0")
implementation("androidx.media3:media3-effect:1.11.0")
implementation("androidx.media3:media3-common:1.11.0")

أنيق

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

تأكّدوا من استبدال 1.11.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();

إذا كان تنسيق وسائط الإدخال يطابق طلب التحويل للصوت أو الفيديو، تنتقل أداة Transformer تلقائيًا إلى نقل الترميز، أي نسخ النماذج المضغوطة من حاوية الإدخال إلى حاوية الإخراج بدون تعديل. يساعد ذلك في تجنُّب التكلفة الحسابية وفقدان الجودة المحتملين لفك الترميز وإعادة ترميز المحتوى بالتنسيق نفسه.

ضبط وضع HDR

إذا كان ملف وسائط الإدخال بتنسيق HDR، يمكنكم الاختيار من بين أوضاع مختلفة لكيفية معالجة أداة Transformer لمعلومات 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
الدعم متوافق مع مستويات واجهة برمجة التطبيقات 31 والإصدارات الأحدث للأجهزة التي تتضمّن برنامج ترميز بإمكانية FEATURE_HdrEditing متوافق مع مستويات واجهة برمجة التطبيقات 29 والإصدارات الأحدث
الأخطاء إذا لم يكن الخيار HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL متوافقًا، يتم استخدام الخيار `HDR_MODE_KEEP_HDR` بدلاً منه. إذا لم يكن الخيار متوافقًا، يتم عرض ExportException.

على الأجهزة التي تتيح إمكانات الترميز المطلوبة وتعمل بالإصدار 13 من نظام التشغيل Android (مستوى واجهة برمجة التطبيقات 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، يمكنكم استخدام FrameProcessor في MediaPipe لإرسال كل إطار من خلال رسم بياني في MediaPipe. يمكنكم الاطّلاع على مثال على ذلك في التطبيق التجريبي Transformer.

معاينة المؤثرات

باستخدام ExoPlayer، يمكنكم معاينة المؤثرات التي تمت إضافتها إلى عنصر وسائط قبل بدء عملية التصدير. باستخدام كائنات Effect نفسها المستخدَمة في EditedMediaItem، استدعوا setVideoEffects() على مثيل ExoPlayer.

Kotlin

val player = ExoPlayer.Builder(context)
    .build()
    .also { exoPlayer ->
        exoPlayer.setMediaItem(inputMediaItem)
        exoPlayer.setVideoEffects(listOf(zoomEffect))
        exoPlayer.prepare()
    }

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();
player.setMediaItem(inputMediaItem);
player.setVideoEffects(ImmutableList.of(zoomEffect));
player.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 لتلقّي إشعارات بشأن أحداث الإكمال أو الخطأ.