ショートビデオ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>