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

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

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

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

البدء

للبدء، أضِف تبعية على وحدات "المحول" و"التأثير" و"الشائعة". في Jetpack Media3:

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

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

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

ضبط وضع النطاق العالي الديناميكية

إذا كان ملف وسائط الإدخال بتنسيق 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. 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 لتلقّي إشعارات بشأن أحداث الاكتمال أو الأخطاء.