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

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

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

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

البدء

للبدء، أضِف عنصرًا تابعًا للوحدات Transformer وEffect وCommon في Jetpack Media3:

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

احرص على استبدال 1.4.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();

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