สร้างแอปตัดต่อวิดีโอพื้นฐานโดยใช้ Media3 Transformer

Transformer API ใน 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

หากไฟล์สื่ออินพุตอยู่ในรูปแบบ HDR คุณสามารถเลือกระหว่างโหมดต่างๆ 2-3 โหมดสำหรับวิธีที่ 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
การสนับสนุน รองรับใน API ระดับ 31 ขึ้นไปสำหรับอุปกรณ์ที่มีโปรแกรมเปลี่ยนไฟล์ที่มีคุณสมบัติ FEATURE_HdrEditing รองรับใน API ระดับ 29 ขึ้นไป
ข้อผิดพลาด หากไม่รองรับ ให้พยายามใช้ HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL แทน หากไม่รองรับ ระบบจะแสดง ExportException

ในอุปกรณ์ที่รองรับความสามารถในการเข้ารหัสที่จำเป็นและใช้ Android 13 (API ระดับ 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 พร้อมกับการเปลี่ยนรูปแบบที่จะใช้กับ 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 เพื่อรับการแจ้งเตือนเกี่ยวกับเหตุการณ์ที่เสร็จสมบูรณ์หรือข้อผิดพลาดได้ด้วย