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

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

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

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

البدء

للبدء، يجب إضافة تبعية على الوحدات الخاصة بكل من Transformer وEffect وCommon في Jetpack Media3:

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

تأكد من استبدال 1.3.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، يمكنك الاختيار من بين بضعة أوضاع مختلفة لكيفية معالجة برنامج 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.

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