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

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

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

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

البدء

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

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

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