ショートビデオSDKは、ビデオマージクラスAliyunIMixComposerを提供します。 このクラスを呼び出して、複数のビデオを1つのオフラインにマージできます。 マージされたビデオは、ピクチャインピクチャ、9平方グリッド、左右分割画面、または上下分割画面などの指定されたレイアウトで配置できます。 ビデオマージ用に複数のビデオトラックを追加できます。 このトピックでは、ショートビデオSDK for Androidを使用してビデオをマージする方法について説明します。 This topic also provides sample code for video merging.

サポートされているエディション

エディション 対応
Professional 必須
標準モード 必須
基本 必須

関連クラス

分類 説明
AliyunIMexComposer 出力パラメーターの設定、トラックの作成、ビデオストリームの追加、マージの開始、コールバックの設定など、ビデオマージのコア機能を定義するクラス。
AliyunMixComposerCreator AliyunIMexComposer実装クラスのインスタンスを作成するために使用されるファクトリクラス。
AliyunMixTrack A class that defines video tracks. このクラスは、ビデオストリームをトラックに追加し、レイアウトとボリュームに関するパラメータを設定するために使用されます。
AliyunMixStream ビデオトラックストリームを定義するクラス。 The class is used to obtain the display mode, file path, and end time of video streams.
AliyunMixOutputParam A class that defines merging output parameters. このクラスは、マージされたビデオの幅と高さ、出力ビットレート、品質レベルなどのパラメータを設定するために使用されます。
AliyunMixCallback コールバックのマージを定義するクラス。 The class is used to set callbacks on merging completion, merging progress, and merging failures.

マージプロセス

設定 手順 説明 サンプルコード
基本 1 マージインスタンスを作成します。 マージインスタンスの作成
2 Create multiple tracks and video streams, and then add the video streams to the tracks separately. Create tracks
3 出力パス、マージされたビデオの幅と高さなどのパラメーターを設定します。 出力パラメータの設定
4 Set callbacks and start merging. ビデオのマージを開始する
5 Destroy the instance and release the resources. リソースのリリース
上級 6 必要に応じて、キャンセル、一時停止、継続機能を設定します。 ビデオのマージを制御する

マージインスタンスの作成

マージインスタンスを作成します。

コードで使用されるパラメーターについては、「関連クラス」をご参照ください

// インスタンスを作成します。
AliyunIMixComposer mixComposer = AliyunMixComposerCreator.createMixComposerInstance();

Create tracks

複数のトラックとビデオストリームを作成し、ビデオストリームを別々にトラックに追加します。 コードで使用されるパラメーターについては、「関連クラス」をご参照ください

// 複数のトラックを作成します。

// トラック1を作成します。
// トラック1のレイアウト。
AliyunMixTrackLayoutParam track1Layout = new AliyunMixTrackLayoutParam.Builder()
        . centerX(0.25f)
        . centerY(0.5f)
        . widthRatio(0.5f)
        . heightRatio(1.f)
        . build();
// トラック1インスタンスを作成します。        
AliyunMixTrack track1 = mixComposer.createTrack(track1Layout);
// トラック1に追加する最初のビデオストリームを作成します。
AliyunMixStream stream11=新しいAliyunMixStream
        . Builder()
        . displayMode(VideoDisplayMode.FILL)
        . filePath("/storage/emulated/0/lesson_01.mp4")
        . streamEndTimeMills(20000)
        . build();
// 最初のビデオストリームをトラック1に追加します。 注: 最近追加されたビデオストリームは、以前に追加されたビデオストリームを上書きします。 最後に追加されたビデオストリームのみが存在します。
track1.addStream(stream11);


// トラック2を作成します。
// トラック2のパラメータ。
AliyunMixTrackLayoutParam track2Layout = new AliyunMixTrackLayoutParam.Builder()
        . centerX(0.75f)
        . centerY(0.5f)
        . widthRatio(0.5f)
        . heightRatio(1.f)
        . build();
// トラック2インスタンスを作成します。
AliyunMixTrack track2 = mixComposer.createTrack(track2Layout);
// トラック2に追加する最初のビデオストリームを作成します。
AliyunMixStream stream21=新しいAliyunMixStream
        . Builder()
        . displayMode(VideoDisplayMode.FILL)
        . filePath("/storage/emulated/0/lesson_02.mp4")
        . streamStartTimeMills(10000)
        . streamEndTimeMills(30000)
        .build();
// Add the first video stream to Track 2. Note: The lately added video stream overwrites the previously added video stream. 最後に追加されたビデオストリームのみが存在します。
track2.addStream(stream21);

出力パラメータの設定

出力パス、マージされたビデオの幅と高さなどのパラメーターを設定します。 コードで使用されるパラメーターについては、「関連クラス」をご参照ください

// 出力パラメータを設定します。
AliyunMixOutputParam outputParam = new AliyunMixOutputParam.Builder()
        . outputPath("/sdcard/output.mp4") // マージされたビデオへのパス。
        . outputAudioReferenceTrack(track2)// トラック2のオーディオがマージされたビデオのオーディオとして使用されることを指定します。 マージされたビデオは1つのオーディオストリームのみをサポートします。
        . outputDurationReferenceTrack(track2) // トラック2のデュレーションがマージされたビデオのデュレーションとして使用されることを指定します。 トラック1の継続時間がこの値より短い場合、トラック1のビデオは最後のフレームで停止します。
        . crf (6)
        . videoQuality(VideoQuality.HD)
        . outputWidth(720) // ビデオの幅。
        . outputHeight(1280) // ビデオの高さ。
        . fps (30) // fps
        . gopSize(30) // gop
        . build();
mixComposer.setOutputParam(outputParam);

ビデオのマージを開始する

コールバックを設定し、ビデオのマージを開始します。 コードで使用されるパラメーターについては、「関連クラス」をご参照ください

// マージを開始します。
AliyunMixCallback callback = new AliyunMixCallback() {
            @Override
            public void onProgress(long progress) {// マージの進捗状況。
                Log.e("MixRecord" 、"onProgress" + progress);
            }

            @Override
            public void onComplete() {
                Log.e("MixRecord", "onComplete"); // マージが完了しました。
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        // コールバックで使用されるスレッドでこのAPI操作を呼び出さないでください。   
                        // マージが完了したら、インスタンスをリリースします。
                        mixComposer.release();
                    }
                });
            }

            @Override
            public void onError(int errorCode) { // マージが失敗します。
                Log.e("MixRecord" 、"onError" + errorCode);
            }
};

リソースのリリース

ビデオがマージされたら、リソースをリリースします。 マージ中にインスタンスを破棄しないでください。 コードで使用されるパラメーターについては、「関連クラス」をご参照ください

mixComposer.release();

ビデオのマージを制御する

必要に応じて、キャンセル、一時停止、継続機能を設定します。 コードで使用されるパラメーターについては、「関連クラス」をご参照ください

// マージを一時停止します。
mixComposer.pause();

// マージを続行します。
mixComposer.resume();

// マージをキャンセルします。
mixComposer.ca ncel();

ビデオマージのサンプルコード

/**
 * ビデオマージの例
 * /
クラスMixActivity : AppCompatActivity() {
    プライベートval REQUEST_TRACK1_STREAM = 1001
    プライベートval REQUEST_TRACK2_STREAM = 1002

    プライベートvarの例: AliyunIMixComposer? =null

    プライベートlateinit var mVideoTrack1 : AliyunMixTrack
    プライベートlateinit var mVideoTrack2 : AliyunMixTrack

    プライベートvar mVideoTrack1Duration = 0L
    プライベートvar mVideoTrack2Duration = 0L
    楽しいonCreate(savedInstanceState: バンドル?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_mix)

        findViewById <ボタン>(R.id.btnReset).setOnClickListener {
            findViewById <ボタン>(R.id.btnMix).isEnabled = false

            mMixComposer?.release()

            init()
        }

        findViewById <ボタン>(R.id.btnAddTrack1Stream).setOnClickListener {
            PermissionX.init(this)
                . 権限 (permissions)
                    Manifest.permission.READ_EXTERNAL_STORAGE、
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
                )
                . request { allGranted, _, _ ->
                    if (allGranted) {
                        PictureSelector.create (この)
                            . openGallery(PictureMimeType.ofVideo())
                            . forResult(REQUEST_TRACK1_STREAM)
                    }
                }
        }

        findViewById <ボタン>(R.id.btnAddTrack2Stream).setOnClickListener {
            PermissionX.init(this)
                . 権限 (permissions)
                    Manifest.permission.READ_EXTERNAL_STORAGE、
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
                )
                . request { allGranted, _, _ ->
                    if (allGranted) {
                        PictureSelector.create (この)
                            . openGallery(PictureMimeType.ofVideo())
                            . forResult(REQUEST_TRACK2_STREAM)
                    }
                }
        }

        findViewById <ボタン>(R.id.btnMix).setOnClickListener {
            // ビデオの制作を開始します。
            valコールバック: AliyunMixCallback = object : AliyunMixCallback {
                override fun onProgress(progress: Long) { // プロダクションの進行状況。
                    Log.e("MixActivity" 、"onProgress $progress")
                }

                オーバーライドfun onComplete() {
                    Log.e("MixActivity" 、"onComplete")
                    ToastUtil.showToast(it.context、「ビデオが制作されました。」)
                }

                override fun onError(errorCode: Int) {
                    Log.e("MixActivity", "onError $errorCode")
                    ToastUtil.showToast(it.context、「ビデオの作成に失敗しました:$errorCode」)
                }
            }

            // 出力パラメータを設定します。
            val outputParamBuilder = AliyunMixOutputParam.Builder()
            outputParamBuilder
                . outputPath("/storage/emulated/0/DCIM/Camera/svideo_mix_demo.mp4")
                . crf (6)
                . videoQuality(VideoQuality.HD)
                . outputWidth(720)
                . outputHeight(1280)
                . fps (30)
                . gopSize (30)
            if(mVideoTrack1Duration > mVideoTrack2Duration) {
                outputParamBuilder.outputAudioReferenceTrack(mVideoTrack1)
                outputParamBuilder.outputDurationReferenceTrack(mVideoTrack1)
            } else {
                outputParamBuilder.outputAudioReferenceTrack(mVideoTrack2)
                outputParamBuilder.outputDurationReferenceTrack(mVideoTrack2)
            }

            mMixComposer!!.setOutputParam(outputParamBuilder.build())
            mMixComposer!!.start (コールバック)
        }

        init()
    }

    private fun init() {
        mMixComposer = AliyunMixComposerCreator.createMixComposerInstance()
        // トラック1を作成します。
        val track1Layout = AliyunMixTrackLayoutParam.Builder()
            . centerX(0.25f)
            . centerY(0.25f)
            . widthRatio(0.5f)
            . heightRatio(0.5f)
            .build()

        mVideoTrack1 = mMixComposer!!.createTrack(track1Layout)

        // トラック2を作成します。
        val track2Layout = AliyunMixTrackLayoutParam.Builder()
            . centerX(0.75f)
            . centerY(0.75f)
            . widthRatio(0.5f)
            . heightRatio(0.5f)
            .build()

        mVideoTrack2 = mMixComposer!!.createTrack(track2Layout)
    }
    override fun onResume() {
        super.onResume()
        mMixComposer?.resume()
    }

    override fun onPause() {
        super.onPause()
        mMixComposer?.pause()
    }

    楽しいonDestroy() {
        super.onDestroy()
        mMixComposer?.release()
    }

    楽しいonActivityResult(requestCode: Int、resultCode: Int、data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode ==アクティビティ. RESULT_OK) {
            when (requestCode) {
                REQUEST_TRACK1_STREAM -> {
                    // onResultコールバック
                    val result = PictureSelector.obtainMultipleResult (データ)

                    mVideoTrack1Duration = 0
                    for (streamBean in result) {

                        // トラック1に追加する最初のビデオストリームを作成します。
                        val stream1 = AliyunMixStream.Builder()
                            . displayMode(VideoDisplayMode.FILL)
                            . filePath(streamBean.realPath)
                            . streamStartTimeMills(mVideoTrack1Duration)
                            . streamEndTimeMills(streamBean.duration)
                            .build()
                        mVideoTrack1Duration += streamBean.duration
                        if(mVideoTrack1.addStream(stream1) == 0) {
                            ToastUtil.showToast (これは「ビデオストリームがトラック1に追加されます」)
                        }

                    }
                }
                REQUEST_TRACK2_STREAM -> {
                    // onResultコールバック
                    val result = PictureSelector.obtainMultipleResult (データ)

                    mVideoTrack2Duration = 0L
                    for (streamBean in result) {

                        // トラック2に追加する最初のビデオストリームを作成します。
                        val stream1 = AliyunMixStream.Builder()
                            . displayMode(VideoDisplayMode.FILL)
                            . filePath(streamBean.realPath)
                            . streamStartTimeMills(mVideoTrack2Duration)
                            . streamEndTimeMills(streamBean.duration)
                            .build()
                        mVideoTrack2Duration += streamBean.duration
                        if(mVideoTrack2.addStream(stream1) == 0) {
                            ToastUtil.showToast (これは「ビデオストリームがトラック2に追加されます」)
                        }
                    }
                }
            }
        }
        if(mVideoTrack1Duration > 0 && mVideoTrack2Duration > 0) {
            findViewById <ボタン>(R.id.btnMix).isEnabled = true
        }
    }
}

XMLのサンプル構成ファイル

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    アンドロイド: layout_width="match_parent"
    アンドロイド: layout_height="match_parent"
    tools:context=".MainActivity">


    <Button
        android:id="@ + id/btnReset"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        アンドロイド: layout_marginTop="76dp"
        android:text="リセット"
        アプリ: layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        アプリ: layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@ + id/btnAddTrack1Stream"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        アンドロイド: layout_marginStart="56dp"
        アンドロイド: layout_marginTop="64dp"
        android:text="ビデオ1を追加"
        アプリ: layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@ id/btnReset"
        app:layout_constraintEnd_toEndOf="@ id/btnReset"
         />

    <Button
        android:id="@ + id/btnAddTrack2Stream"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        アンドロイド: layout_marginTop="64dp"
        アンドロイド: layout_marginEnd="56dp"
        android:text="ビデオ2を追加"
        アプリ: layout_constraintStart_toStartOf="@ id/btnReset"
        app:layout_constraintTop_toBottomOf="@ id/btnReset"
        app:layout_constraintEnd_toEndOf="parent"
        />

    <Button
        android:id="@ + id/btnMix"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        アンドロイド: layout_marginBottom="176dp"
        android:text="ビデオの制作を開始"
        android:enabled="false"
        アプリ: layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        アプリ: layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>