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

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

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

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

البدء

للبدء، يجب إضافة تبعية إلى الوحدات الخاصة بكل من Transformer وEffect وCommon في 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"

احرص على استبدال 1.4.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، يمكنك الاختيار من بين عدة أوضاع مختلفة لكيفية معالجة 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 بدلاً منه. إذا لم يكن متوافقًا، يتم طرح ExportException.

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