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

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

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

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

البدء

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

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

تأكّدوا من استبدال 1.10.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 بدلاً منه. إذا لم يكن هذا الوضع غير متاح، يتم عرض 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). على سبيل المثال، يمكنكم استخدام a 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 لتلقّي إشعارات بشأن أحداث الإكمال أو الخطأ.