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

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

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

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

البدء

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

Kotlin

implementation("androidx.media3:media3-transformer:1.11.1")
implementation("androidx.media3:media3-effect:1.11.1")
implementation("androidx.media3:media3-common:1.11.1")

أنيق

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

تأكَّد من استبدال 1.11.1 بالإصدار المفضّل لديك من المكتبة. يمكنك الرجوع إلى ملاحظات الإصدار للاطّلاع على أحدث إصدار.

الصفوف المهمة

الفئة الغرض
Transformer بدء عمليات التحويل وإيقافها والتحقّق من آخر المعلومات عن مستوى تقدّم عملية تحويل جارية
EditedMediaItem تمثّل ملف وسائط مطلوب معالجته والتعديلات التي يجب تطبيقها عليه.
Effects مجموعة من المؤثرات الصوتية والمرئية

ضبط الناتج

باستخدام Transformer.Builder، يمكنك الآن تحديد videoMimeType وaudioMimetype مباشرةً من خلال ضبط الدالة بدون الحاجة إلى إنشاء عنصر TransformationRequest.

تحويل الترميز بين التنسيقات

يوضّح الرمز التالي كيفية ضبط عنصر Transformer لإخراج فيديو بتنسيق H.265/AVC وصوت بتنسيق الترميز المتقدّم للصوت:

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

Kotlin

val player = ExoPlayer.Builder(context)
    .build()
    .also { exoPlayer ->
        exoPlayer.setMediaItem(inputMediaItem)
        exoPlayer.setVideoEffects(listOf(zoomEffect))
        exoPlayer.prepare()
    }

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();
player.setMediaItem(inputMediaItem);
player.setVideoEffects(ImmutableList.of(zoomEffect));
player.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 ليتم إعلامك بأحداث الإكمال أو الخطأ.