---
lang: ja
path: user-guide/recording/recording-development-guide
labels: ユーザーガイド/録音・録画/開発ガイド
metaTitle: 開発ガイド ｜ 録音・録画 ｜ ユーザーガイド ｜ SkyWay（スカイウェイ）
---

# 開発ガイド

## P2P での通話内容を録音・録画する方法

ユーザー間の通信を行う P2P 通信とは別に、録音・録画機能を利用するための SFU 通信を同時に利用することで、ユーザー間の通信は P2P 通信を利用しながら録音・録画機能を利用できます。

![ユーザー間の通信に P2P 通信を利用しながら録音・録画機能を利用する際の概要図](/media/posts/docs/recording/development/p2p-rec-overview.png)

### SFU 通信料と SFU リソース確保料について

P2P 通信での通話を録音・録画する場合、SkyWay Auth Token に次のような設定を行った上で `maxSubscribers` の設定を `0` として publish を行うことで、録音・録画に伴う[SFU 通信料と SFU リソース確保料](/ja/pricing/)が発生しなくなります。
本設定は SkyWay Auth Token のバージョン 3 から対応しております。

※ [SkyWay Auth Token / SFU リソースについて](/ja/docs/user-guide/authentication/skyway-auth-token/#room-リソース)

```js
{
  // ...省略
  version: 3,
  scope: {
    // ...省略
    rooms: [
      {
        // ...省略
        sfu: {
          enabled: true,
          maxSubscribersLimit: 0,
        },
        // ...省略
      }
    ]
  }
}
```

publish 時の `maxSubscribers` オプションを `0` に設定すると、その Publication は subscribe ができなくなり、録音・録画専用の Publication として扱われます。

### 注意事項

SkyWay の 各種 SDK では一度 Publish した Stream を再度 Publish することができません。

P2P 通信と SFU 通信を併用する際に同一のマイク、カメラをそれぞれ「ユーザー間通信」と「録音・録画」で用いる場合は、 Stream を 2 回ずつ取得する必要があります。

### JavaScript SDK での実装例

```js
// トークンはサーバーサイドで生成してください
const token = new SkyWayAuthToken({
  jti: uuidV4(),
  iat: nowInSec(),
  exp: nowInSec() + 60 * 60 * 24,
  version: 3,
  scope: {
    appId: 'ここにアプリケーションIDをペーストしてください',
    rooms: [
      {
        id: '*',
        name: 'recordingRoom',
        methods: ['create', 'close', 'updateMetadata'],
        sfu: {
          enabled: true,
          maxSubscribersLimit: 0,
        },
        member: {
          id: '*',
          name: '*',
          methods: ['publish', 'subscribe', 'updateMetadata'],
        },
      },
    ],
  },
}).encode('ここにシークレットキーをペーストしてください');

const context = await SkyWayContext.Create(token);

const room = await SkyWayRoom.FindOrCreate(context, {
  name: 'recordingRoom',
});
const me = await room.join();

const { audio: communicationAudio, video: communicationVideo } =
  await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream();
// ユーザー間の通信にはP2P通信を利用します
await me.publish(communicationAudio, { type: 'p2p' });
await me.publish(communicationVideo, { type: 'p2p' });

// 一度PublishしたStreamは再度Publishすることができないので、改めて録音・録画用のStreamを取得します
const { audio: recordingAudio, video: recordingVideo } =
  await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream();

// 録音録画機能を利用するために、SFU通信を利用します
const recordingAudioPublication = await me.publish(recordingAudio, {
  type: 'sfu',
  maxSubscribers: 0
});
const recordingVideoPublication = await me.publish(recordingVideo, {
  type: 'sfu',
  maxSubscribers: 0
});

// 録音録画を開始します
// startRecording関数はあくまでも例であり、実際はユーザーのアプリケーションにて実装していただく処理です
await startRecording([recordingAudioPublication.id, recordingVideoPublication.id]);
```

## 想定されるエラー

録音・録画機能を利用する際に発生する可能性のあるエラーについて説明します。

### SkyWay Recording API のエラーレスポンス

#### 400

リクエストに含まれるパラメータに問題があります。ドキュメントを参照して正しい値をパラメータに入れるように修正してください。

#### 401

有効な SkyWay Admin Auth Token が使用されていません。[SkyWay Admin Auth Token のドキュメント](/ja/docs/user-guide/authentication/skyway-admin-auth-token/)を参照して正しいトークンを使用してください。

#### 403

クラウドストレージのクレデンシャルに対応する権限が不足しており、クラウドストレージを正常に利用できません。[各クラウドストレージに必要な資格情報](/ja/docs/user-guide/recording/recording-overview/#各クラウドストレージに必要な資格情報)の項目を参照して正しいクレデンシャルを使用してください。

#### 404

対象のリソースが存在しません。

#### 429

レートリミットもしくはリソース量の制限に違反しています。[制限と割当](/ja/docs/user-guide/commons/quotas-and-limits/)の項目に記載されている範囲内で利用してください。

#### 500

Recording サーバーにおいてエラーが発生しています。リクエストの処理が完了していない可能性があるため、リクエストを再試行してください。
特に、 DeleteRecordingSession の場合は、録音・録画の処理が停止せず、意図しない料金が発生する可能性があります。
複数回再試行を行なってもエラーが解消しない場合は、SkyWay のサポートに問い合わせてください。

### 録音・録画中のエラー

録音・録画処理において、各種クラウドストレージに録音・録画ファイルをアップロードする際に、何らかのエラーが発生した場合、再試行して解決可能な場合は自動的に再試行を行います。再試行して解決できなかった場合、録音・録画が中断され、新しい録音・録画ファイルとして一度だけ再開されます。

再試行に失敗した場合、録音・録画ファイルのメタデータの status が FAILED になり、errors フィールドにエラーの内容が追記されます。

格納されるエラーは大きく分けて 2 種類あり、1 つは SkyWay 側のエラー、もう 1 つはクラウドストレージ側のエラーです。

#### SkyWay 側のエラー

SkyWay のエラーは `Internal Server Error:` という文字列から始まります。

SkyWay のエラーが発生している場合は、録音・録画の処理が途中で終了している可能性があります。

#### クラウドストレージ側のエラー

クラウドストレージのエラーは、お使いのクラウドストレージの公式ドキュメントを参照し、原因の特定と対処を行ってください。

- Google Cloud Storage

  [トラブルシューティング  |  Cloud Storage  |  Google Cloud](https://cloud.google.com/storage/docs/troubleshooting?hl=ja)

  [HTTP status and error codes for JSON  |  Cloud Storage  |  Google Cloud](https://cloud.google.com/storage/docs/json_api/v1/status-codes)

- Amazon S3

  [Error responses - Amazon Simple Storage Service](https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html)

  [Amazon S3 のエラーに関するベストプラクティス - Amazon Simple Storage Service](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/ErrorBestPractices.html)

- Wasabi

  [REST API Introduction](https://docs.wasabi.com/docs/rest-api-introduction)

### エラーの対応方法

#### 検知方法

録音・録画処理中のエラーはメタデータの errors フィールドに出力されます。

よって、以下のいずれかの方法でエラーの検知ができます。

- [GetRecordingSession API](https://recording.api-reference.skyway.ntt.com/#/paths/~1v1~1rooms~1%7BroomId%7D~1sessions~1%7BsessionId%7D/get) を定期的に呼び出し、レスポンスをチェックする
- 録音・録画終了後にクラウドストレージ上のメタデータファイルの内容をチェックする

録音・録画が中断された場合、GetRecordingSession APIの結果とクラウドストレージ上のメタデータファイルの内容が異なる場合があります。その場合正しい内容はGetRecordingSession APIの結果となります。

#### 再試行

Recording サーバー側で可能な限り再試行を行っています。

再試行で解決ができなかったエラーに関しては、メタデータの errors フィールドや、SkyWay 並びに各種クラウドストレージの障害情報を確認し、エラーがクラウドストレージのものであればクラウドストレージのサポートに問い合わせを行ってください。

## エラーとなった場合の録音・録画ファイルの取得

録音・録画ファイルのアップロードが何らかの理由でエラーとなった場合、録音・録画ファイルのステータスは FAILED になります。ここではクラウドストレージごとに修正方法を説明します。

### Google Cloud Storage

録音・録画ファイルの構造はステータスが RECORDING の場合と同様になります。
そのため録音・録画ファイルを再生する場合は分割された一時保存ファイルを結合することで再生できるようになります。
手順を次に示します。

1. 対象の録音・録画ファイルの webm ファイルをすべてダウンロードします。
2. ダウンロードしたファイルを次のコマンドで結合します。

```bash
ls -v *.webm | xargs cat > output.webm
```

### Amazon S3

録音・録画したデータを Amazon S3 に保存する際は、MultipartUpload という仕組みを利用します。MultipartUpload がエラーとなった場合は、Amazon S3 に MultipartUpload のデータが残り続ける可能性があります。

エラー発生時点までのデータをファイルとして保存したい場合は、CompleteMultipartUpload の操作を行い、MultipartUpload の処理を完了させる必要があります。

また、MultipartUpload のデータは課金対象となります。エラー発生時点までのデータが不要な場合は、バケットライフサイクルによる自動削除の設定を行うか、AbortMultipartUpload の操作を行い、MultipartUpload を完全に終了させてください。

本記事では、AWS CLI を用いて、CompleteMultipartUpload の操作を行いエラー発生時点までのデータをファイルとして保存する方法と、AbortMultipartUpload の操作を行い MultipartUpload を完全に終了させる方法について説明します。

バケットライフサイクルによる自動削除の設定については、Amazon S3 のドキュメントを参照してください。

[不完全なマルチパートアップロードを削除するためのバケットライフサイクル設定の設定 - Amazon Simple Storage Service](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/mpu-abort-incomplete-mpu-lifecycle-config.html)

[不完全なマルチパートアップロードをクリーンアップするための Amazon S3 ライフサイクル設定ルールを検証する](https://repost.aws/ja/knowledge-center/s3-multipart-cleanup-lifecycle-rule)

また、本記事で説明している内容は、変更されている可能性があります。

必要に応じて、Amazon S3 の公式ドキュメントを参照してください。

[マルチパートアップロードを使用したオブジェクトのアップロードとコピー - Amazon Simple Storage Service](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/mpuoverview.html)

#### エラー発生時点までのデータをファイルとして保存する方法

- 以下のコマンドを実行し、MultipartUpload のリストを取得します。
  `aws s3api list-multipart-uploads --bucket <bucket name>`
- 実行結果の Uploads の中から、対象の Key と UploadId を選択します。
- 次に、以下のコマンドを実行し、MultipartUpload に含まれる Parts のリストを取得します。
  `aws s3api list-parts --bucket <bucket name> --key <Key> --upload-id <UploadId>`
- 実行結果の Parts の中から、PartNumber と ETag の組を全て選択します。
- 次に、以下のコマンドを実行し、エラー発生時点までの MultipartUpload のデータをファイルとして保存します。
  `aws s3api complete-multipart-upload --multipart-upload 'Parts=[{ETag="<ETag_1>",PartNumber=<PartNumber_1>},{ETag="<ETag_2>",PartNumber=<PartNumber_2>}, ... ]' --bucket <bucket name> --key <Key> --upload-id <UploadId>`

なお、`Parts` には、`list-parts` で取得した Parts を全て指定してください。

#### MultipartUpload を完全に終了させる方法

- 以下のコマンドを実行し、MultipartUpload のリストを取得します。
  `aws s3api list-multipart-uploads --bucket <bucket name>`
- 実行結果の Uploads の中から、対象の Key と UploadId を選択します。
- 以下のコマンドを実行し、MultipartUpload を完全に終了させます。これにより、MultipartUpload のデータが削除されます。
  `aws s3api abort-multipart-upload --bucket <bucket name> --key <Key> --upload-id <UploadId>`

### Wasabi

録音・録画したデータを Wasabi に保存する際は、MultipartUpload という仕組みを利用します。MultipartUpload がエラーとなった場合は、Wasabi に MultipartUpload のデータが残り続ける可能性があります。

エラー発生時点までのデータをファイルとして保存したい場合は、CompleteMultipartUpload の操作を行い、MultipartUpload の処理を完了させる必要があります。

また、MultipartUpload のデータは課金対象となります。エラー発生時点までのデータが不要な場合は、AbortMultipartUpload の操作を行い、MultipartUpload を完全に終了させてください。

なお、Wasabi では、中断された MultipartUpload のデータは[30 日後に自動で削除](https://docs.wasabi.com/docs/how-does-wasabi-handle-multipart-uploads)されます。

本記事では、AWS CLI を用いて、CompleteMultipartUpload の操作を行いエラー発生時点までのデータをファイルとして保存する方法と、AbortMultipartUpload の操作を行い MultipartUpload を完全に終了させる方法について説明します。

また、本記事で説明している内容は、変更されている可能性があります。

必要に応じて、Wasabi の公式ドキュメントを参照してください。

[How do I clean up my failed multipart uploads? – Wasabi Knowledge Base](https://docs.wasabi.com/docs/how-do-i-clean-up-my-failed-multipart-uploads)

#### エラー発生時点までのデータをファイルとして保存する方法

- 以下のコマンドを実行し、MultipartUpload のリストを取得します。
  `aws s3api list-multipart-uploads --bucket <bucket name> --endpoint-url=<wasabi endpoint>`
- 実行結果の Uploads の中から、対象の Key と UploadId を選択します。
- 次に、以下のコマンドを実行し、MultipartUpload に含まれる Parts のリストを取得します。
  `aws s3api list-parts --bucket <bucket name> --key <Key> --upload-id <UploadId> --endpoint-url=<wasabi endpoint>`
- 実行結果の Parts の中から、PartNumber と ETag の組を全て選択します。
- 次に、以下のコマンドを実行し、エラー発生時点までの MultipartUpload のデータをファイルとして保存します。
  `aws s3api complete-multipart-upload --multipart-upload 'Parts=[{ETag="<ETag_1>",PartNumber=<PartNumber_1>},{ETag="<ETag_2>",PartNumber=<PartNumber_2>}, ... ]' --bucket <bucket name> --key <Key> --upload-id <UploadId> --endpoint-url=<wasabi endpoint>`

なお、`Parts` には、`list-parts` で取得した Parts を全て指定してください。

#### MultipartUpload を完全に終了させる方法

- 以下のコマンドを実行し、MultipartUpload のリストを取得します。
  `aws s3api list-multipart-uploads --bucket <bucket name> --endpoint-url=<wasabi endpoint>`
- 実行結果の Uploads の中から、対象の Key と UploadId を選択します。
- 以下のコマンドを実行し、MultipartUpload を完全に終了させます。これにより、MultipartUpload のデータが削除されます。
  `aws s3api abort-multipart-upload --bucket <bucket name> --key <Key> --upload-id <UploadId> --endpoint-url=<wasabi endpoint>`

## 録音・録画ファイルの修正方法

保存先クラウドストレージとして Amazon S3、または Wasabi を利用し、保存された録音・録画ファイルを再生する際に次のような事象が発生します。

- ファイルの総再生時間が実際の録音・録画時間と大きく異なる
  - 総再生時間が 1 時間と表示される
- シーク機能が適切に機能しない

また、ファイルが破損していると表示されることがあります。

これは、録音・録画の処理に起因するもので、メディアデータ自体は正常に保存されています。

録音・録画ファイルを正常に再生するためには、ffmpeg というツールを用いて、記録された録音・録画ファイルに対して以下の処理を実行することで、正常に再生できるようになります。

```sh
ffmpeg -i original.webm -c copy -y fixed.webm
```

なお、ffmpeg のインストール方法については、[ffmpeg の公式ドキュメント](https://ffmpeg.org/download.html)を参照してください。

映像のコーデックにH264を利用している場合は、ffmpegが直接処理できないので次のように拡張子を一旦変更してから処理してください。

```sh
mv original.webm original.mkv
ffmpeg -i original.mkv -c copy -y fixed.mkv
mv fixed.mkv fixed.webm
```

## 安全な実装方法

録音・録画機能を安全に利用するための注意事項について説明します。

録音・録画機能を利用する際に扱う識別子として以下があります。

- Room ID
- Publication ID
- Publisher ID
- Publisher Name

これらの 識別子 をクライアントサイドからサーバーサイドアプリケーションへ送信して録音・録画を開始する場合、送信者が 識別子 を差し替えることで意図しない Publication が録音・録画される可能性があります。

これらの 識別子 をクライアントサイドから取得する代わりに次の手法を採用することを検討してください。なお、ここで使用する SkyWay Room API の詳細な仕様は次の記事を参照してください。

[SkyWay Room API](/ja/docs/user-guide/rtc-api-server/room-api)

### サーバサイドで createRoom を行う

createRoom をクライアントサイドではなくサーバーサイドアプリケーションで SkyWay Room API を用いて実行することで常に正しい Room ID を得る事ができます。

次のようにして SkyWay Room API で createRoom を行うことができます。

```bash
curl -X POST https://room.skyway.ntt.com/v1/json-rpc \
     -H "Content-Type: application/json" \
     -H "Authorization: Bearer YOUR_SKYWAY_ADMIN_AUTH_TOKEN" \
     -d '{ "jsonrpc": "2.0", "id": "YOUR_RANDOM_UUID", "method": "createRoom", "params": { "name": "YOUR_ROOM_NAME" }}'
```

### Room Name から Room ID を取得する

サーバーサイドアプリケーションで Room Name を払い出している場合に有効な手法です。

Room Name はサーバーサイドアプリケーションで払い出すことができるため、
SkyWay Auth Token でその Room Name を指定していれば、Room Name は信用できる情報として扱うことができます。
また、SkyWay Room API の findRoom で name として Room Name を指定することで、Room ID を取得できます。

次のようにして SkyWay Room API で Room Name から Room ID を取得できます。

```bash
curl -X POST https://room.skyway.ntt.com/v1/json-rpc \
     -H "Content-Type: application/json" \
     -H "Authorization: Bearer YOUR_SKYWAY_ADMIN_AUTH_TOKEN" \
     -d '{ "jsonrpc": "2.0", "id": "YOUR_RANDOM_UUID", "method": "findRoom", "params": { "name": "YOUR_ROOM_NAME" }}'\
      | jq -r '.result.room.id'
```

### サーバサイドアプリケーションで Publication のリストを取得する

録音・録画対象の Publication の ID を指定する場合はクライアントサイドからサーバーサイドアプリケーションに Publication の ID を送信するのではなく、SkyWay Room API を用いて信用できる Publication のリストを取得することをおすすめします。

次のようにして SkyWay Room API で Publication のリストを取得できます。

```bash
curl -X POST https://room.skyway.ntt.com/v1/json-rpc \
     -H "Content-Type: application/json" \
     -H "Authorization: Bearer YOUR_SKYWAY_ADMIN_AUTH_TOKEN" \
     -d '{ "jsonrpc": "2.0", "id": "YOUR_RANDOM_UUID", "method": "findRoom", "params": { "name": "YOUR_ROOM_NAME" }}' \
      | jq -r '.result.room.publications'
```

## RecordingSession の管理

RecordingSession が作成されると、以下のいずれかの状況になるまでは録音・録画の処理が継続され、指定されたクラウドストレージに対して録音・録画ファイルの送信が行われます。

- RecordingSession が削除される
  - RecordingSession は、以下のいずれかのタイミングで削除されます
    - DeleteRecordingSession API を呼び出す
    - RecordingSession 作成から 7 日間が経過する
    - RecordingSession と紐づく Room(Room)が削除される
- 録音・録画対象となっている Publication が unpublish される
  - Publication は以下のいずれかのタイミングで unpublish されます
    - 明示的に SDK の unpublish の処理を実行する
    - 当該の Publication を publish している Member が Room(Room) から退出する
    - 当該の Publication が存在する Room(Room) が削除される
- 録音・録画開始から 12 時間経過する

これらのうち、サーバーサイドアプリケーションから録音・録画処理を停止させる方法は、DeleteRecordingSession API を利用し、RecordingSession の削除を行うことです。

したがって、DeleteRecordingSession API を利用するための、以下の情報は適切に管理・記録する必要があります。

- Room ID
- RecordingSession ID

これらの情報を紛失してしまうと、録音・録画開始から 12 時間が経過するか、Publication が unpublish されるまでは録音・録画の処理が行われ、課金が発生し続けてしまいます。サーバーサイドアプリケーションから録音・録画処理を停止させたい場合は、上記の情報を適切に管理・記録するようにしてください。

## 各クラウドストレージで発生するオペレーション・リクエスト

SkyWay の録音・録画機能では、録音・録画ファイルのアップロード先としてユーザーのクラウドストレージを使用します。したがって、ユーザー側でクラウドストレージのオペレーション・リクエストに伴う料金が発生します。オペレーション・リクエストに伴う料金は、その種類と回数によって決まります。

本記事では、録音・録画機能を利用する際に発生するオペレーション・リクエストの種類と回数について説明します。

### Google Cloud Storage

以下の条件で録音、及び録画処理を行った場合、以下の回数のオペレーションが実行されます。

なお、オペレーションの回数はあくまでも概算であり、実際の回数は状況によって増える可能性があります。

- 録音
  - ビットレート約 32 kbps
- 録画
  - 1920 x 1080 (FHD)
  - 30 FPS
  - ビットレート約 5 Mbps

| 録音・録画時間(時間) | 録音に伴うクラス A オペレーション回数 | 録音に伴うクラス B オペレーション回数 | 録画に伴うクラス A オペレーション回数 | 録画に伴うクラス B オペレーション回数 |
| -------------------- | ------------------------------------- | ------------------------------------- | ------------------------------------- | ------------------------------------- |
| 0.5                  | 80                                    | 70                                    | 260                                   | 260                                   |
| 1                    | 130                                   | 130                                   | 510                                   | 500                                   |
| 2                    | 250                                   | 260                                   | 990                                   | 1000                                  |
| 3                    | 370                                   | 380                                   | 1480                                  | 1500                                  |
| 4                    | 480                                   | 500                                   | 1970                                  | 1990                                  |
| 5                    | 600                                   | 600                                   | 2460                                  | 2460                                  |
| 6                    | 710                                   | 720                                   | 2950                                  | 2950                                  |
| 7                    | 830                                   | 840                                   | 3440                                  | 3450                                  |
| 8                    | 950                                   | 970                                   | 3920                                  | 3940                                  |
| 9                    | 1060                                  | 1060                                  | 4410                                  | 4410                                  |
| 10                   | 1180                                  | 1190                                  | 4900                                  | 4910                                  |
| 11                   | 1300                                  | 1310                                  | 5390                                  | 5400                                  |
| 12                   | 1410                                  | 1430                                  | 5880                                  | 5900                                  |

なお、実際に発生する料金は、バケットの設定などによって異なります。

詳しくは、[Google Cloud Storage の料金ページ](https://cloud.google.com/storage/pricing?hl=ja#operations-pricing)を参照してください。

なお、録音・録画処理では、オブジェクトの書き込みだけでなく、削除の操作も行われます。したがって、Standard ストレージ以外のストレージクラスを利用している場合は、早期削除料金が発生します。録音・録画ファイルの保存先バケットには、Standard ストレージを利用することを強くおすすめします。

### Amazon S3

以下の条件で録音、及び録画処理を行った場合、以下の回数の「PUT、COPY、POST、LIST リクエスト」が実行されます。

なお、リクエストの回数はあくまでも概算であり、実際の回数は状況によって増える可能性があります。

- 録音
  - ビットレート約 32 kbps
- 録画
  - 1920 x 1080 (FHD)
  - 30 FPS
  - ビットレート約 5 Mbps

| 録音・録画時間(時間) | 録音に伴うリクエスト回数 | 録画に伴うリクエスト回数 |
| -------------------- | ------------------------ | ------------------------ |
| 0.5                  | 4                        | 230                      |
| 1                    | 5                        | 450                      |
| 2                    | 8                        | 890                      |
| 3                    | 11                       | 1330                     |
| 4                    | 14                       | 1770                     |
| 5                    | 17                       | 2210                     |
| 6                    | 20                       | 2650                     |
| 7                    | 23                       | 3090                     |
| 8                    | 26                       | 3530                     |
| 9                    | 29                       | 3970                     |
| 10                   | 32                       | 4410                     |
| 11                   | 35                       | 4850                     |
| 12                   | 38                       | 5290                     |

なお、実際に発生する料金は、バケットの設定などによって異なります。

詳しくは、[Amazon S3 の料金ページ](https://aws.amazon.com/jp/s3/pricing/)を参照してください。

### Wasabi

Wasabi では、リクエスト回数に伴う料金は発生しません。

## 録音のホワイトノイズを軽減させる方法

Opus DTX を有効化した状態で録音を行うと、録音ファイルにホワイトノイズが含まれる場合があります。

Opus DTX は、音声が無音に近い状態の場合に、送信されるデータ量を大幅に削減するための機能です。
Recording サーバーでは、 Opus DTX が有効になっている場合でも正常にファイルを保存できるようにするための処理を行っています。
これにより、特に音声が無音に近い状態となっていた箇所でホワイトノイズが含まれる可能性があります。

Opus DTX はデフォルトで有効となっているため、Opus DTX を無効化する際は Publication の publish 時に明示的に設定する必要があります。
Opus DTX を無効化する方法は、各 SDK のドキュメントを参照してください。

- [JavaScript SDK](https://javascript-sdk.api-reference.skyway.ntt.com/core/types/CodecParameters.html)
- [iOS SDK](<https://ios-sdk.api-reference.skyway.ntt.com/core/Classes/SKWCodecParameters.html#/c:objc(cs)SKWCodecParameters(py)useDTX>)
- [Android SDK](https://android-sdk.api-reference.skyway.ntt.com/core/core/com.ntt.skyway.core.content/-codec/-parameters/index.html)

## 録音ファイルの合成方法

複数の音声ファイルを1つのファイルに結合するには、ffmpeg等の外部のツールを利用する必要があります。ここでは、ffmpegの利用方法とその注意点を説明します。

ffmpegのダウンロードは[公式サイト](https://www.ffmpeg.org/download.html)をご参照ください。
なお、詳細な利用方法は[ffmpegの公式ドキュメント](https://www.ffmpeg.org/documentation.html)をご参照ください。

### ffmpeg を使用した録音ファイルの基本的な合成方法

ffmpegを使用して、2つの音声ファイルを結合するには、以下のようにコマンドを実行します。

```sh
ffmpeg \
-i audio_1.webm \
-i audio_2.webm \
-filter_complex \
"[0:a][1:a]amerge=inputs=2[out]" \
-map "[out]" \
-ac 2 \
output.m4a
```

- `-i` オプションで入力ファイルを指定します。これらのファイルは1番目から順番に`[0]`, `[1]`, ... として参照できます。`[0:a]`とすると、1番目のファイルの音声を指定できます。
- `-filter_complex` オプション以下で、詳細なフィルタを指定します。
- `amerge=inputs=2` は、2つの音声を1つの音声に結合するフィルタを指定します。このフィルタの出力先には `[out]` というタグを付けます。
- `-map` オプションで、`[out]` タグの音声を出力として指定します。
- `-ac` オプションで、出力ファイルのチャンネル数を指定します。この例では、2チャンネルに設定しています。
- `output.m4a` は、出力ファイル名を指定します。


### Opus の仕様による音ズレの修正方法

FFmpeg などのツールで録音ファイルを合成すると、 Opus の仕様により音ズレが発生する可能性があります。

次のようにリサンプリングを行うことでこの問題を回避できます。

```sh
ffmpeg \
-i audio_1.webm \
-i audio_2.webm \
-i audio_3.webm \
-filter_complex \
"[0:a]aresample=async=2000:first_pts=0, aformat=channel_layouts=stereo[a0]; \
 [1:a]aresample=async=2000:first_pts=0, aformat=channel_layouts=stereo[a1]; \
 [2:a]aresample=async=2000:first_pts=0, aformat=channel_layouts=stereo[a2]; \
 [a0][a1][a2]amerge=inputs=3[out]" \
-map "[out]" \
-ac 2 \
output.m4a
```

- `aresample` フィルタを使用してリサンプリングを行い、音ズレを修正します。修正したファイルにはそれぞれ`[a0]`, `[a1]`, `[a2]`というタグを付けています。
- `amerge=inputs=3`フィルタを使用して修正した3つの音声`[a0]`, `[a1]`, `[a2]`を結合します。

### 録音ファイルの開始時刻の調整方法

録音開始時刻が異なる複数の音声ファイルを合成する場合、各音声ファイルに調整が必要になります。

録音開始時刻は[metadata](https://skyway.ntt.com/ja/docs/user-guide/recording/recording-tips/#録音-録画ファイルのメタデータの活用)を参照することで、`createdAt` というパラメータから取得することができます。

`createdAt` を比較した結果 audio_2.webm の開始時刻が1秒遅れていた場合、以下のようにして調整を行います。

```sh
ffmpeg \
-i audio_1.webm \
-i audio_2.webm \
-filter_complex \
"[0:a]aresample=async=2000:first_pts=0, aformat=channel_layouts=stereo[a0]; \
 [1:a]aresample=async=2000:first_pts=0, aformat=channel_layouts=stereo[a1]; \
 [a1]adelay=1000:all=1[a1_delay]; \
 [a0][a1_delay]amerge=inputs=2[out]" \
-map "[out]" \
-ac 2 \
output.m4a
```

- `adelay` フィルタを使用して、音声の開始時刻を調整します。この例では、`[a1]` の開始時刻を1秒遅らせ、`[a1_delay]` というタグを付けています。
- `amerge=inputs=2` フィルタを使用して、調整した2つの音声`[a0]`, `[a1_delay]`を結合します。
