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

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

  • تعديل فيديو من خلال القطع وتغيير الحجم والتدوير
  • إضافة تأثيرات، مثل العناصر المركّبة والفلاتر
  • معالجة التنسيقات الخاصة، مثل فيديوهات 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();

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

ضبط وضع 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). على سبيل المثال، يمكنك استخدام 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، يمكنك إنشاء تأثيرات مخصّصة تناسب حالات الاستخدام الخاصة بك. في المثال التالي، استخدِم subclass 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 لتلقّي إشعارات بشأن أحداث الاكتمال أو الخطأ.