Amazon Chime SDK を用いて音声通話機能をアプリに組み込む

サービス開発部の伊藤です。

以前、Amazon Chime SDK を利用しチャットアプリへ音声通話機能を組み込むという技術検証を実施したことがあり、今回はそこで得た知見をまとめたいと思います。本記事は、ヌーラボブログリレー2024 for Tech Advent Calendar 2024 の 12/9 公開分になります。

Amazon Chime SDK について

Amazon Chime SDK (以降、Chime SDKと略します)をご存知でしょうか。Chime SDK は AWS が公開しているリアルタイム通信機能をアプリケーションに組みこむためのライブラリです。こちらを利用することで、音声・ビデオ通話機能をアプリに組み込むことができるようになります。

AWS公式サイト – Amazon Chime SDK
https://aws.amazon.com/jp/chime/chime-sdk/

実際に使ってみる中で、次のメリットを感じました。

  • Amazon Chime と同じ通信基盤を利用できるため信頼性が高い
  • インフラストラクチャの管理を最小化できる
  • AWSの他サービスと連携により拡張性が高い

Amazon Chime と同じ通信基盤を利用できるため信頼性が高い

Chime SDK を知らない方でも、Amazon Chime は使ったことがあるという方はいるのではないでしょうか。Amazon Chime は AWS のビデオミーティングや通話を行うサービスであり、Google Meet や Zoom のようにミーティング用 URL を発行し、オンラインでミーティングを実施できます。

Chime SDK はその名の通り、Amazon Chime のミーティング・通話機能を部分的にアプリに組み込むための開発キットです。Chime SDK を用いて通話機能を組み込んだアプリは、Amazon Chime と同じ通信基盤を利用してミーティングを実施できます。

音声やビデオなどのメディアデータ通信は大量のデータをさばくことが想定されます。そのため、実行基盤の準備には相応の調査や検証が必要になりますが、既に実績のある基盤を利用できるのは信頼性の面で大きなメリットといえます。

インフラストラクチャの管理を最小化できる

今回の技術検証では WebRTC を用いた音声通話機能を実装しました。仮にそれらを動かす環境を自前で用意するとすると、WebRTC を利用するための環境(シグナリングサーバーや TURN / STUN サーバーなど)の構築が必要になります。また、運用においても配信に伴う負荷に対しスケーラビリティを確保する必要があり、セキュリティ面なども考えると多大なコストがかかります。

Amazon Chime SDK を利用することでそれらをAWSの基盤に任せられるので、その分だけインフラストラクチャにかかる構築・運用コストを下げることができます。開発者は音声通話といったリアルタイムコミュニケーション機能をサーバーレスアーキテクチャとして開発することができ、開発リソースをアプリケーションの機能改善やユーザー体験の向上に集中させることが可能になります。

AWSの他サービスとの連携により拡張性が高い

Chime SDK の優れている部分として、他の様々な AWS サービスと連携させられる点があげられます。これにより、「通話ができるようになった。やったー!」で終わりでなく、取得したメディアデータを活用し機能を発展させることであらゆるユースケースへの対応が可能になります。

ここで、AWSが公開しているChime SDKのデモアプリを紹介します。こちらのデモは Chime SDK のほとんどの機能が実装されています、また、ソースコードも公開されているので開発を進める際にも、かなり参考になりました。

amazon-chime-sdk-js/demos/serverless
https://github.com/aws/amazon-chime-sdk-js/tree/main/demos/serverless

デモアプリのビデオ通話機能を私が使っている様子になります。

Amazon Chime SDK の公式デモアプリを使っている様子

実装されている機能の一部にラベルをつけています。みてもらうとわかる通り、ビデオ通話機能の基本的な機能が網羅されており、中にはリアルタイム翻訳といった高度な機能も含まれています。

リアルタイム翻訳については、Amazon Chime SDKの他に、Amazon Transcribe と Amazon Translate を有効化することで利用できます。処理の流れとしては、Chime SDK から取得した通話のメディアデータを Amazon Transcribe で文字情報としてテキスト化し、それを Amazon Translate に渡すことで任意の言語へリアルタイムに変換しています。

このように、AWS の他サービスと連携することで簡単に機能拡張できる点も Chime SDK を使う利点といえます。ちなみに、私が最後にデモアプリを試してから1年近く経過しているので、現在はより多くの機能が搭載されていると思います。

技術検証で実装した内容について

技術検証で開発した音声通話機能について紹介します。

音声通話機能

音声通話機能の説明

通話に参加したいトピックを選択し、画面左下のメニューでヘッドホンのアイコンを押すと通話を開始できます。通話中の音声にあわせて、話者のアイコンにエフェクトをつけ誰が話しているのかがわかるようにしました。また、マイクをミュートしている場合は、他の参加者にも状態がわかるようにミュートアイコンが表示されます。

画面共有機能

画面共有機能の説明

左下メニューから画面共有を実施できるようにしています。共有された画面を別のウィンドウで取り出すことができます。これにより、ユーザーは共有された画面を見ながら、調べ物やチャット通知の確認といった他の作業も並行できるようにしています。

各種設定

各種設定の説明

オーディオデバイスの選択は左下のメニューの歯車アイコンからすぐに実施できるようにしています。それに加え、All voice chat setting から設定画面にいくことで、いくつかオプション機能を設定できるようにしました。

Enable noise cancelling を有効にすると、生活音がマイクにのることを軽減できます。その他にも、

  • 音声通話に参加した際にユーザーの状態を自動的に通話中(ヘッドホンマーク)に変更する
  • 通話開始時に自動的にマイクをミュートにする

という設定項目を用意しました。

アーキテクチャとデータフロー

アーキテクチャ

実装したシステムのアーキテクチャについて説明します。このシステムは、フロントエンド、バックエンド、そして複数のAWSサービスを組み合わせて構成されています。

システム構成図

システム構成は以下の通りです:

  • フロントエンド
    ユーザーが操作するウェブアプリケーションです。ここでは、画面上の通話開始ボタンが押されたことをトリガーに、Chime SDK の javascript ライブラリを用いて WebRTC セッションを開始し、音声通話を実現しています。また、javascript の他に android および ios 向けのライブラリも提供しており、モバイル対応も可能です。
  • バックエンド
    ミーティングの作成・参加者管理を行うために、AWS SDK を使って Chime の API を呼び出しています。内部 API を介してフロントエンドと連携しています。
  • Chime メディアエンドポイント
    WebRTC を使った音声データの中継を行うエンドポイントです。
  • イベント管理とキュー
    Amazon EventBridge と Amazon SQS を使用してミーティングイベントをトリガーしたのち、AWS Lambda がキューを処理し Amazon ElastiCache for Redis へ書き込むことでミーティングの参加者情報などを管理します。
  • ストレージ
    Amazon Aurora と Amazon ElastiCache for Redis を使っています。Aurora にはアプリのユーザー情報などの永続的なデータを保存し、Redis にミーティングや参加者といった一時的な情報を保存しています。Redis への書き込みを契機に Redis Pub/Sub で後続の処理を走らせています。今回は既存システムの構成の兼ね合いで Redis を使っていますが、Amazon Dynamo DB を採用し Dynamo DB Streams で後続の処理を実行するのもアリだと思います。
  • メッセージングサーバー
    Socket.io を使用し、リアルタイムかつシームレスにフロントエンドの画面を更新しています。

シーケンス図で見るデータフロー

音声通話は以下のシーケンスで処理を実施しています。

処理全体のシーケンス図

下記の観点でシーケンスについて説明します。

  • 音声通話を実施する
  • ミーティングの状態変更をリアルタイムにアプリへ伝える

音声通話を実施する

① 最寄りの Media Region を取得する

最初の手順は、フロントエンドから Chime の公開 API へアクセスし、最寄りの Media Region を取得します。シーケンスでは図の赤枠部分になります。

最寄りの Media Region を取得する

公開 API(https://nearest-media-region.l.chime.aws/​)にアクセスすると、以下のレスポンスが返ります。

Media Region として利用できるリージョンのうちもっともフロントエンドを表示する端末の位置に近いリージョンを返しています。このデータは以降の手順で必要になります。

Media Region(メディアリージョン)とは

メディアリージョンというのは、Amazon Chime SDKが音声やビデオなどのメディアデータを処理するために使用する地理的なデータセンターの場所を指します。メディアリージョンを設定する目的は、主に遅延(レイテンシー)の最小化のためです。

ユーザーとサーバー間の物理的な距離が近いほど、データの伝送時間が短くなります。これにより、音声やビデオの遅延が減少し、スムーズなコミュニケーションが可能になります。今回の技術検証では、公開APIを利用してメディアリージョンをユーザーの最寄りのリージョンに自動設定するようにしていますが、特定の値を与えてメディアリージョンを固定することも可能です。

どんな場合にリージョンを固定するかというと、一部の国や地域でデータの保存場所や伝送に関する規制があった場合にそれを回避する目的で行うケースが考えられます。要件に応じて、自動設定する or 固定するか を検討してください。

② ミーティングデータを作成する

次に、フロントエンドから内部API経由でトリガーをかけ、バックエンドから Chime に対し CreateMeeting API を呼びます。

ミーティングデータを作成する

ミーティングを作成するためのAPIリクエストは、ClientRequestToken と MediaRegion を指定します。これにより、Chime 側でミーティングデータが作られ、Meeting IDが発行されます。

CreateMeeting APIのリクエスト例
POST /meetings HTTP/1.1
Host: service.chime.aws.amazon.com
Content-Type: application/json

{
  "ClientRequestToken": "unique-client-request-token",
  "MediaRegion": "ap-northeast-1", 
  "ExternalMeetingId": "project-team-meeting-2023-10-15"
}
  • ClientRequestToken
    クライアント側で生成する一意のトークンで、リクエストの冪等性を保証します。
  • MediaRegion
    手順①で取得した値です。
  • ExternalMeetingId
    開発者が管理するミーティングの一意の識別子です。最大64文字で、アプリケーション内でのミーティング管理に使用します。
CreateMeeting APIのレスポンス例

リクエストが成功すると、以下のようなレスポンスが返されます。

HTTP/1.1 201 Created
Content-Type: application/json

{
  "Meeting": {
    "MeetingId": "example-meeting-id",
    // ~ 略 ~
  }
}
  • MeetingId
    作成されたミーティングの一意の識別子で、後続のAPI呼び出しで使用します。

③ ミーティングの出席者を作成する

次に、バックエンドから Chime の CreateAttendee API を呼び出し、得られたデータをフロントエンドに返却します。

ミーティングの出席者を作成する

CreateAttendee APIのリクエスト例

特定のミーティングに参加者を追加するためのAPIリクエストでは、MeetingId ExternalUserIdを指定します。

POST /meetings/{MeetingId}/attendees HTTP/1.1
Host: service.chime.aws.amazon.com
Content-Type: application/json

{
  "ExternalUserId": "user-id-123"
}
  • MeetingId
    手順②で取得した値です。
  • ExternalUserId
    クライアント側で任意に設定できるユーザーの識別子です。最大で64文字を指定でき、今回はアプリ内で管理しているアカウントのIDを設定しています。この値は、WebRTC セッションの確立後に Chime からリアルタイムに取得できる各ユーザーのデータに設定されます。この値に含まれるIDを参照することで、アプリ内のどのユーザーのデータなのか判別します。
CreateAttendee APIのレスポンス例
HTTP/1.1 201 Created
Content-Type: application/json

{
  "Attendee": {
    "ExternalUserId": "user-id-123",
    "AttendeeId": "abcd1234-5678-90ab-cdef-1234567890ab",
    "JoinToken": "Njc4OTBhY2QtMTIzNC00NTY3LTg5MGEtYmNkZWZnaGlqa2w="
  }
}
  • AttendeeId
    Amazon Chime が生成する参加者の一意の識別子です。メディアセッションやシグナリングで使用されます。
  • JoinToken
    参加者がミーティングに参加するための認証トークンです。後の手順でフロントエンドから WebRTC セッションを開始する際に必要となります。

④ WebRTC セッションを開始/切断する

ここまでの説明で、音声通話を開始するために必要なデータが準備できました。ここからは、フロントエンドと Amazon Chime 間で WebRTC セッションを確立する流れを説明します。シーケンスでは図の赤枠部分になります。

WebRTC セッションを開始/切断する

今回のアプリは、音声通話開始ボタン(id: joinButton)を設置し、ボタンをクリックされたことをトリガーに音声通話を開始します。フロントエンド側の実装を以下に示します。説明をわかりやすくするために、コードは簡略化しています。

const {
  ConsoleLogger,
  DefaultDeviceController,
  DefaultMeetingSession,
  MeetingSessionConfiguration,
  LogLevel,
} = ChimeSDK;

document.getElementById('joinButton').addEventListener('click', async () => {
  // 1. ミーティングと参加者情報を取得
  const response = await fetch('/join', { method: 'POST' });
  const joinInfo = await response.json();

  // 2. MeetingSessionConfigurationの作成
  const configuration = new MeetingSessionConfiguration(
      joinInfo.Meeting,
      joinInfo.Attendee
  );

  // 3. ロガーとデバイスコントローラーの初期化
  const logger = new ConsoleLogger('ChimeMeetingLogs', LogLevel.INFO);
  const deviceController = new DefaultDeviceController(logger);

  // 4. MeetingSessionの作成
  const meetingSession = new DefaultMeetingSession(
      configuration,
      logger,
      deviceController
  );

  // 5. オーディオの取得と開始
  // 入力デバイス
  const audioInputDevices = await meetingSession.audioVideo.listAudioInputDevices();
  deviceIdIn = audioInputDevices[0].deviceId   // デフォルトのインプットを指定
  await this.meetingSession.audioVideo.startAudioInput(deviceIdOut)
  // 出力デバイス
  const audioOutputDevices = meetingSession.audioVideo.listAudioOutputDevices()
  deviceIdOut = audioOutputDevices[0].deviceId   // デフォルトのアウトプットを指定
  await this.meetingSession.audioVideo.chooseAudioOutput(deviceIdOut)

  // 6. オーディオをバインド
  const audioMix = document.getElementById('meetingAudio');
  await meetingSession.audioVideo.bindAudioElement(audioMix);

  // 7. セッションを開始
  meetingSession.audioVideo.start();
});

実装の内容を説明します。

1. ミーティングと参加者情報を取得

冒頭の部分では、画面上のボタンのクリックがされたらバックエンドへPOSTリクエストを送っています。バックエンドの挙動は手順②③で説明した内容です。

document.getElementById('joinButton').addEventListener('click', async () => {

// 1. ミーティングと参加者情報を取得
const response = await fetch('/join', { method: 'POST' });
const joinInfo = await response.json();

// ----  (略)

joinInfo には、WebRTCを開始するために必要な情報(Meeting/Attendee/JoinTokenなど)が含まれています。

2. MeetingSessionConfigurationの作成

次に、MeetingSessionConfiguration を作成します。

// 2. MeetingSessionConfigurationの作成
const configuration = new MeetingSessionConfiguration(
    joinInfo.Meeting,
    joinInfo.Attendee
);

configuration は、Meeting Session を作成するために必要になります。joinInfo内の Meeting と Attendeeを対象のミーティングへ参加するための configuration を生成します。

3. ロガーとデバイスコントローラーの初期化

次に、ロガーとデバイスコントローラを初期化します。

// 3. ロガーとデバイスコントローラーの初期化
const logger = new ConsoleLogger('ChimeMeetingLogs', LogLevel.INFO);
const deviceController = new DefaultDeviceController(logger);

ロガーは ChimeSDK のログをどの程度出力するかを制御します。ログレベルを INFO 以上に指定すると、ブラウザの開発ツール上のコンソールにログが流れてくるようになりトラブルシューティングに役立ちます。今回はINFO で設定していますが、他にも DEBUGERRORWARNOFFから指定できます。必要に応じて設定を変更してください。ロガーを作ったら、ブラウザからオーディオデバイスにアクセスするために必要となるデバイスコントローラーを生成します。

4. MeetingSessionの作成

先の手順2と3で生成した configuration、logger、deviceController を元に meetingSessionを生成します。

// 4. MeetingSessionの作成
const meetingSession = new DefaultMeetingSession(
    configuration,
    logger,
    deviceController
);

今後は、meetingSession から必要な処理を呼び出します。

5. オーディオの取得と開始

通話で使用するオーディオデバイスを指定します。

// 5. オーディオの取得と開始
  // 入力デバイス
  const audioInputDevices = await meetingSession.audioVideo.listAudioInputDevices();
  deviceIdIn = audioInputDevices[0].deviceId   // デフォルトのインプットを指定
  await this.meetingSession.audioVideo.startAudioInput(deviceIdIn)
  // 出力デバイス
  const audioOutputDevices = meetingSession.audioVideo.listAudioOutputDevices()
  deviceIdOut = audioOutputDevices[0].deviceId   // デフォルトのアウトプットを指定
  await this.meetingSession.audioVideo.chooseAudioOutput(deviceIdOut)

入出力デバイスのリストを取得するために、 meetingSession.audioVideolistAudioInputDevices および listAudioOutputDevices を呼び出しています。今回は端末側のデフォルトに指定されている入出力デバイスに常に接続する形にしています。

6. オーディオをバインド

オーディオデバイスを選択したら、画面上のaudio要素に接続します。

// 6. オーディオをバインド
const audioMix = document.getElementById('meetingAudio');
await meetingSession.audioVideo.bindAudioElement(audioMix);

これにより、WebRTCセッションから流れ込んでくる音声データをブラウザごしにオーディオデバイスへ入出力できるようになります。ここまでくれば音声通話を開始する準備は完了です。

7. セッションを開始

WebRTC セッションを開始します。

// 7. セッションを開始
meetingSession.audioVideo.start();

meetingSession.audioVideo.start() でWebRTCセッションが開始され音声通話がはじまります。また、meetingSession.audioVideo.stop() を呼ぶとWebRTCセッションを終了できます。

⑤ ミーティングから出席者を削除する

最後に、音声通話切断後の流れを説明します。フロントエンドから内部 API 経由でトリガーをかけ、バックエンドから Chime に対し DeleteAttendee API を呼びます。下図の赤枠部分です。

ミーティングから出席者を削除する

DeleteAttendee APIのリクエスト例
DELETE /meetings/{MeetingId}/attendees/{AttendeeId} HTTP/1.1 
Host: service.chime.aws.amazon.com

URIに参加していたミーティングの MeetingId とユーザーの AttendeeId を含めて DELETEリクエストを送ります。これにより、Chime 側でミーティングに紐づく対象ユーザーの参加情報が削除されます。

⑥ ミーティングデータの削除は明示的に呼び出さない

手順⑤で参加者データを削除する API を呼び出しましたが、ミーティングデータの削除に関しては API を呼ばず、Chime 側の自動削除処理に任せています。

ミーティングデータの削除は明示的に呼び出さない

Chime では以下の条件で Meeting を削除します。

  • ミーティングが終了してから 5 分経過
    Chime SDKのミーティングデータは、最後の参加者がミーティングから退室してから5分後に自動的に削除されます。これは、ミーティングの非アクティブ状態を検知するための仕組みで、参加者が全員退室している場合にのみ有効です。
  • 作成からミーティング有効期限(最大24時間)を超過
    CreateMeeting APIでミーティングを作成すると、そのミーティングは最大で24時間まで利用可能です。ミーティング作成から24時間経過すると、参加者の有無にかかわらずミーティングデータは自動で削除され、再利用することはできません。

これらの条件により、Amazon Chime SDKでは不要なデータが長期的に保持されることを防ぎ、効率的にリソースを管理しています。

(余談) ミーティングの終わりはどこなのか?

ミーティングの終わりとは、いったいどこなんでしょうか。

これは、本機能を開発するうえで悩んだポイントの一つでした。ミーティングの終了時刻がきたときでしょうか。参加者全員が会議室からいなくなったときでしょうか。自身の経験をもとに考えてみましたが、どれも曖昧なものに思えます。オフラインのミーティングでは、終わりの時間がきてもしれっと延長することはよくありますし、会議室をでた後に移動しながら議論が続いてることもよくあります。人間は適当なのです。

単にアプリ上でユーザー同士がリアルタイムに話せるようにするという要件を満たすだけなら問題ありませんが、将来的にミーティングデータを起点にあらゆるデータ(自動で文字に起こした議事録、ミーティング内で共有された資料など)を紐づけて管理したくなるといったことが考えられます。例えば、ミーティングが終わった後、会議音声を自動的に文字起こしし特定のフォーマットにあわせて整理された議事録が出力されると良さそうです。さらに、Backlogの課題に添付されたら最高です。それも、全自動で!しかし、先に述べた人間の適当さにより、ミーティング外で結論が決定された場合は議事録に残すことはできません。データの活用まで含めると、ミーティングがどこからどこまでなのかは非常に重要な意味をもちます。

結論として、そもそもミーティングを時間で区切るべきではないという結論に至りました。ミーティングとは”ある目的を達成するために話し合う場でしかない”ので、ミーティング側で終了条件を持つこと自体がおこがましいのです。そういった経緯で(?)、DeleteMeeting APIを呼ぶことはしていません。とはいえ、システム的に無期限というわけにもいかないので、Chime側のライフサイクルに合わせて削除するようにしています。

ミーティングの状態変更をリアルタイムにアプリへ伝える

① Chimeのイベントを捕捉する

以下のイベントパターン指定する EventBridge ルールを追加し、Chimeイベントを捕捉します。

{
  "source": ["aws.chime"],
  "detail-type": ["Chime Meeting State Change"],
  "detail": {
    "eventType": [
      "chime:AttendeeJoined",
      "chime:AttendeeLeft",
      "chime:AttendeeDropped"
    ]
  }
}

各イベントは下記のユーザーの行動時に発火します。

  • chime:AttendeeJoined
    ユーザーが音声通話に参加したとき
  • chime:AttendeeLeft
    ユーザーが音声通話から退室したとき
  • chime:AttendeeDropped
    ユーザーが音声通話から意図せず退室してしまったとき

正常にミーティングへ参加 / 退室した場合は、chime:AttendeeJoined / chime:AttendeeLeftイベントが発火します。外的要因やユーザーの操作ミスなどで意図せず参加者がミーティングから退室した場合には、chime:AttendeeDroppedイベントが発火します。例えば、ネットワークの接続状況が悪かったり、間違えてブラウザのタブを閉じてしまった場合などです。シーケンス図でいうと下記の赤枠の箇所になります。

Chimeのイベントを捕捉する

② ChimeのイベントをトリガーにStoreを更新する

Chime側のイベントがとれるようになったので、イベントに合わせSQSメッセージを発行し Lambda でSubscribeします。メッセージの内容をもとに、Redis へ書き込みます。

ChimeのイベントをトリガーにStoreを更新する

アーキテクチャ図でいうと以下矢印の流れになります。

ChimeのイベントをトリガーにStoreを更新する

③ Storeの更新をトリガーに画面に変更内容を反映する

Lambda から Redis に書き込まれたことを契機に、Redis の Pub/Sub を使い WebSocket を経由してフロントの画面に反映させます。これにより、他のユーザーがミーティングを開始した際、ミーティングが開始されたチャンネルに効果をつけたり参加者数を増減させたりを画面更新なしに実行できます。

Storeの更新をトリガーに画面に変更内容を反映する

より快適なユーザー体験の追求

ここまでの説明で、音声通話機能を利用することができるようになりました。しかし、いまのアプリはユーザーにとって大変不親切な挙動となっています。なぜなら、現状のアプリではユーザーは音声通話が開始されていることと参加者数以外のことはわからないからです。

現状のままだと、ユーザーは次の不安を抱くと思います。

  • いま聞こえている音声は、他の参加者のうちいったい誰の声なのか
  • 他の参加者はいつからミーティングに入っていたのか
  • 他の参加者はマイクをONにしているのか(もしくはミュート状態なのか)

きっと次のような機能があると嬉しいことでしょう。

  • ユーザーの声に合わせて話者のアイコンにエフェクトをつける
  • 他のユーザーの入退室にあわせて効果音を鳴らす
  • 他者がマイクをミュートしたらミュートアイコンを出す

上記は一例ですが、音声通話のリアルタイムな状態変化が画面に反映されると嬉しいケースは多く考えられます。音声通話中の状態変化を画面にリアルタイムに更新できれば、より快適なユーザー体験を実現できるのではないでしょうか。ここからは、Chime SDK を使って音声通話中の状態変化をリアルタイムに画面を反映する方法を解説します。

次の観点で説明を進めていきます。

  • 音声通話中の状態の変化を画面に反映する
  • ノイズキャンセリング機能を実装する

音声通話中の状態の変化を画面に反映する

Amazon Chime SDKでは、ミーティング中のデータを取得する方法として

  • Observer
  • Realtime API

の2つが提供されています。それぞれの違いは、取得するデータの性質やタイミング、使い勝手にあります。それぞれについて説明します。

Observerを用いた通話データの取得

Observerは、特定のイベントが発生した際にコールバック関数が自動的に呼び出される仕組みです。Chime SDKでは、次のような Observer を利用できます。

  • AudioVideoObserver

    音声やビデオの状態変化(例えば、ユーザーがミーティングに参加・退出したり、音声の接続状況が変わったりする場合)に応じてイベントを受け取ることができます。

  • DeviceChangeObserver

    カメラやマイクの接続状況の変化を監視します。

  • VolumeIndicatorObserver

    参加者の音声レベルやミュート状況の変化を監視します。

特徴

  • 受動的

    Observerは特定のイベントが発生した際に呼び出されるため、イベントに対して受動的にデータを受け取ります。

  • イベント駆動型

    音声の状態やビデオの変更など、発生したイベントに対する情報を取得するのに便利です。

  • 簡単な導入

    特定のイベントが発生したときに呼び出されるので、状態の変化をすばやくキャッチするのに役立ちます。

使用例

  • 参加者がミーティングに参加または退出する際に通知を受ける。
  • ユーザーがミュートにした場合や音声のボリュームレベルが変化した際の情報を取得する。

Realtime APIを用いた通話データの取得

Realtime APIは、ミーティング中のデータをプログラムで直接取得または操作できるAPIです。例えば、参加者の現在の音声レベルやミュート状況、ミーティング内でのリアルタイムメッセージなどにアクセスできます。

特徴

  • 能動的

    Realtime APIは、必要なときに状態を取得するために呼び出すことができ、Observerのようにイベントの発生を待つ必要はありません。

  • 即時アクセス

    必要なタイミングで最新の情報を即時に取得可能です。ミーティングの状態に対する細かい制御やカスタムなインタラクションを構築するのに向いています。

  • 柔軟性

    特定のイベントが発生しなくても必要な情報を取得したり、特定の動作をトリガーしたりすることが可能です。

使用例

  • 参加者リストをリアルタイムで確認する。
  • 特定のトリガーや条件に応じて参加者の状態(ミュートやボリュームなど)を動的に取得・操作する。

ObserverとRealtime APIの使い分け

Observerは、イベントベースでミーティングの状態を監視したい場合に適しています。特定のタイミングで自動的に通知が欲しいときに便利です。

Realtime APIは、状態を即時に取得したり、任意のタイミングでデータを取得したい場合に向いています。カスタマイズ性が高いため、柔軟な制御やリアルタイムの反応が必要な場合にはRealtime APIが適しています。

どちらもAmazon Chime SDKの機能を最大限に活用するための方法ですが、Observerは主にイベントに基づいて反応するのに対し、Realtime APIは特定の状態や情報を能動的に確認するために利用する、という違いがあります。用途に応じて使い分けると良いでしょう。

ノイズキャンセリング機能の実装

Chime SDK は Voice Focus というノイズを除去する機能を提供しています。この機能は非常に強力で、高精度のノイズキャンセリング機能を簡単に実装できます。具体的には、先のコードの入力デバイスを選択する箇所に、VoiceFocusDeviceTransformer を挟むだけです。実装例を以下に示します。

// ------ (略) ------ 

// ミーティングセッションのインスタンスを作成
const meetingSession = new DefaultMeetingSession(
  meetingSessionConfiguration,
  logger,
  deviceController
);

// 音声フォーカスを初期化する関数
async function initializeVoiceFocus() {
  const voiceFocusSpec = { category: 'voicefocus', name: 'default', variant: 'c10' }
  const transformer = await VoiceFocusDeviceTransformer.create(voiceFocusSpec);
  const audioInputDevice = await meetingSession.audioVideo.chooseAudioInputDevice('default');
  
  if (await transformer.isSupported()) {
    // ボイスフォーカスがサポートされている場合は、トランスフォームデバイスを作成
    const voiceFocusDevice = await transformer.createTransformDevice(audioInputDevice);

    // ミーティングで音声デバイスを選択する際に、ボイスフォーカスを有効にする
    await meetingSession.audioVideo.chooseAudioInputDevice(voiceFocusDevice);
  } else {
    // サポートされていない場合は通常のデバイスを使用
    await meetingSession.audioVideo.chooseAudioInputDevice(audioInputDevice);
  }
}

// ミーティングの初期化
async function startMeeting() {
  // ミーティングの音声ビデオ接続を開始
  await meetingSession.audioVideo.start();

  // ボイスフォーカスを初期化して有効化
  await initializeVoiceFocus();
}

// ボタンをクリックしてミーティングを開始
document.getElementById('startMeetingButton').addEventListener('click', () => {
  startMeeting();
});

コードの解説

  1. VoiceFocusSpecの作成
    • VoiceFocusSpec を作成します。このプロパティを変えることで、ノイズキャンセルの精度を調整できます。C100, C50, C20, C10, autoから選択でき、デフォルトはautoです。
  2. VoiceFocusDeviceTransformerの作成:
    • VoiceFocusDeviceTransformer.create()に1で作ったVoiceFocusSpecを渡し、ボイスフォーカスのトランスフォーマーデバイスを作成します。これは、ミーティング参加者の音声から背景ノイズを除去するためのオブジェクトです。
  3. ボイスフォーカスのサポート確認:
    • transformer.isSupported()メソッドを使用して、現在のデバイスがボイスフォーカス機能をサポートしているか確認します。
  4. ボイスフォーカスのトランスフォームデバイスの作成:
    • サポートされている場合、transformer.createTransformDevice()メソッドで、ボイスフォーカスの変換デバイスを作成し、それを音声入力デバイスとして設定します。
    • サポートされていない場合は、通常の音声入力デバイスを使用します。
  5. ミーティングの開始:
    • startMeeting関数では、ミーティングセッションのaudioVideo.start を呼び出して音声接続を開始したのちにボイスフォーカス機能を有効にします。

注意点① ブラウザが対応しているかチェックが必要

ボイスフォーカスの利用は、全てのブラウザで利用できるわけではなく、一定スペックを満たしているブラウザ上のみに限定されます。そのため、コード上で利用条件をクリアしているか確認する必要があります。具体的には、上記のコードで isSupported() を呼んでいる部分です。どの環境で利用可能かは下記を参考にしてください。

Can I use Amazon Voice Focus and Echo Reduction in my application?
https://aws.github.io/amazon-chime-sdk-js/modules/amazonvoice_focus.html#can-i-use-amazon-voice-focus-and-echo-reduction-in-my-application

注意点② ノイズキャンセルの強度と処理性能はトレードオフ

ノイズキャンセルのかかり具合は、VoiceFocusSpec の Variant の値を変えることで調整できます。設定できる値は C100C50C20C10auto のいずれかであり、デフォルトはautoです。C100 が最も品質が高く、数字が下がるごとに品質は下がりノイズ除去の強度は落ちます。

注意点としては、Variant の値をあげればノイズキャンセルの強度はあがりますが、これらの処理はクライアント側で実行されるため、処理コストが高まりブラウザが重くなってしまいます。そのため、利用する環境に合わせてノイズキャンセルの強度と性能のバランスをみて設定値を決定する必要があります。

私が使ってみた感じでは、最低レベルの C10 でも十分にノイズをカットしてくれると感じました。むしろ、C10 からあげても大きな違いは感じられませんでした。想定される利用シーン次第ではありますが、マイクに生活音が入ることをカットしたい程度の用途であれば、C10 設定で十分かと思います。

おわりに

リモート環境で仕事を進めるには、テキストコミュニケーションベースの非同期なやりとりがメインになると思います。一方で、リアルタイムにサクッと話して解決したい、という同期的なやりとりを欲する状況も少なからずあるのではないでしょうか。

とはいえ、WebRTCを利用できる環境をイチから構築するのはなかなか大変です。通話機能はあると嬉しいけど実現するにはハードルが高い…という方には特に Amazon Chime SDK をおすすめします。開発および運用コストを抑えつつ、高品質な音声通話を実現したいというニーズを満たせるのではないでしょうか。

ちなみに本記事は音声通話機能の開発についてのお話でしたが、ビデオ通話についてもフロントの実装を少し変えるだけで実装できます。ぜひぜひお試しください!

開発メンバー募集中

より良いチームワークを生み出す

チームの創造力を高めるコラボレーションツール

製品をみる