Amazon Connectで音声をふきこみ、ふきこんだ音声で一斉発信してみた

# きっかけ
災害等の危険がある時に、
自治会長さん等の地域のリーダーの「声」で防災無線を使って呼びかけることで、
地域のみなさんの避難行動が促された、というニュースを先日見ました。
（テレビの映像を見る限り、専用の装置やシステムをつかっているようでした）

ふと、
Amazon Connectに電話をかけて、音声をふきこみ、録音する。
その録音された音声を、一斉発信することはできないだろうか？…と考えました。

# やってみたこと

ふきこみ・録音・再生を**すべて電話で**やってみました。  

1.　Amazon Connectに電話をかけると、**留守番機能**のようにメッセージが録音される。
2.　録音した音声がAmazon Connectのプロンプトで**再生可能な形式で保存**される。
3.　Amazon Connectから**一斉発信**すると、（着信先で）**録音した音声が再生される**。

[**【デモ動画】こんな感じになります！**](https://youtu.be/u8w61VM-2C8)

https://youtu.be/u8w61VM-2C8

# 実現するのに必要な要素
やりたいことを実現するため、上記の**太字部分**をどのように実現するか、検討しました。
末尾、参考とした記事の考え方を学ばせていただき、組み合わせることで実現しました。
大きく**４つの要素**があります。

**【留守電機能】**
　Amazon Connectの問い合わせフローで、Kinesis Video Streamに音声を渡し、S3に保存する。  

**【再生可能な形式で保存】**
　8bitモノラル、8kHz、μ-law、.wav形式で保存する必要がある。
　（Amazon ConnectのプロンプトでS3から再生する場合、この形式が必須）  

**【一斉発信】**
　一斉発信する宛先の電話番号をDynamoDBに格納する。
　Lambdaでこの電話番号一覧を取得し、Amazon Connectにアウトバウンドの指示を出す。  

**【録音した音声が再生される】**
　Amazon Connectの問い合わせフローで、S3バケットから音声ファイルを指定する。

# 前提となる環境
* Lambdaレイヤーを使用します。参考記事で使用している、ebml、FFmpegは事前にダウンロードし、zipファイルをLambdaレイヤーにそれぞれ追加しておきます。
  * ebmlはフォルダnodejs下部に保存し、zipします。
  * FFmpegはフォルダbin下部に保存し、zipします。
* S3は、3つのバケットが必要です。
  * Kinesis Video Streamのストリーム情報を保存するバケット（streamInfo）
  * ストリームデータにヘッダを追加し.wavファイルを保存するバケット（wav-pcm）
  * PCMからμ-lawに変換した.wavファイルを保存するバケット(wav-mulaw)
  * それぞれ、バケットポリシーでLambdaやAmazon Connectを許可しておく必要があります。
* Amazon Connectの準備が必要です。
  * 電話番号を取得しておく必要があります。
  * 問い合わせフローを、音声ふきこみ用と、音声再生用の２つを用意します。
  * インスタンスエイリアス→問い合わせフロー→AWS Lambdaで、今回使用するLambdaのアクセス許可をします。
* 複数の電話番号に発信する場合、DynamoDBの準備が必要です。
  * Lambda内にベタ書きすることもできますが、DynamoDBに電話番号を登録する方が楽です。
  * scanまたはqueryで取得した電話番号一覧をもとに、「connect.start_outbound_voice_contact」で外線発信します。
  * 電話番号は「+81」で始まる形式（E.164形式）で保存します。
* Lambdaでプログラミングが必要です。
  * 音声ファイルの生成・変換：Node.js（Node.js 16.x）
  * 一斉発信：Python（Python 3.9）

# つくってみた
前提となる環境を大まかに図化するとこのようになります。
実現するのに必要な要素４つについて具体的に何をしたのか説明します。
![oraho2_aws_architect.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1597092/e664ed6e-b449-36fa-8d41-b0f07e2aff22.png)
* [**【留守電機能】**](https://dev.classmethod.jp/articles/amazon-connect-voice-mail-from-kinesis-video-stream/)については、ほぼリンク先の記事の考え方のとおり作りました。
  * バケット（wav-pcm）に.wavファイルが保存されるまで、実現できます。
  * 録音用の問い合わせフローは以下のようになります。少し、コツがあります。
    * メディアストリーミングの開始の合図「ピピっ」という発信を入れること。
    * ストリーミングの停止後、さらにLambda（streamInfo）関数を呼び出した後に、音声の登録が完了した旨のプロンプトを再生すること。
    　（吹き込んでも、データが生成される前に切断されてしまうのを防ぎます）
    ![rec_contactflow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1597092/24e9eaa9-c813-08b7-65b5-13757722c1ed.png)

* [**【再生可能な形式で保存】**](https://dev.classmethod.jp/articles/ffmpeg-lambda-layer/)するためには、留守電機能で保存した音声を、μ-law形式に変換する必要があります。
  * 上記リンク先を参考に、FFmpegをLambdaレイヤーに登録し、Node.jsで扱えるようにします。
  * changeMulaw.js は**S3バケットwav-pcmに.wavファイルがPUTされたタイミングで起動**するよう、**トリガーを追加**しておきます。
  * また、[**こちらの記事**](https://xp-cloud.jp/blog/2018/05/18/3345/)を参考にしつつ、参考リンクの51行目のexecSyncを下記のように変更すると、μ-law形式に変換することができます。（srcDataやdestDataの拡張子は.wavに変更）
    * **-y　オプション**をつけることで、新しい音声ファイルができた際に上書きします。（このオプションをつけないと初回はOKでも２回目ふきこみ時にエラーになります）
    * **-acodec pcm_u8**　で　**8bit**　に変換しています。
    * **-c:a pcm_mulaw**　で　**μ-law**　に変換しています。
    * 変換後は、参考リンクの５８行目の保存名を「mulaw.wav」にしています。
```changeMulaw.js
execSync('ffmpeg -y -i /tmp/srcData.wav -acodec pcm_u8 -c:a pcm_mulaw /tmp/destData.wav');
```

* [**【一斉発信】**](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/connect.html#Connect.Client.start_outbound_voice_contact)するためには、電話番号をリスト化し、Amazon Connectに外線発信の指示を順次出していく必要があります。
  * **connect.start_outbound_voice_contact**　と宣言すると外線発信できます。
  * DynamoDBに電話番号,名前,年齢層等のデータを登録しておき、scanまたはqueryして電話番号リストをlistにします。
    * テストする場合は、listに"+8190xxxxxxxx"にように書いてもOKです。
    * ごくシンプルにfor文で繰り返し処理をしていますが、架電対象電話番号数が多い場合やエラーハンドリングを行う場合は、StepFunctionsを使用して制御する方が安全です。

```python.py
    list = [("DynamoDBからScan等した電話番号をリストとして格納")] #テスト用なら2〜３番号ベタ打ちもアリ
    connect = boto3.client("connect")
    contactflow_id = "(問い合わせフローのコンタクトフローID)"
    instance_id = "（Amazon ConnectのインスタンスID）"
    source_phone_number = "（Amazon Connectで取得した電話番号）"

    for dest_number in list:
        print(f"calling to {dest_number}")
        connect.start_outbound_voice_contact(
            DestinationPhoneNumber = dest_number,
            ContactFlowId = contactflow_id,
            InstanceId = instance_id,
            SourcePhoneNumber = source_phone_number,
            )
````

* [**【録音した音声が再生される】**](https://dev.classmethod.jp/articles/amazon-connect-dynamic-voice-playback-s3/)ためには、S3のバケットを指定する必要があります。
  * 今回は、**s3://(バケット名)/mulaw.wav**　のようにS3ファイルパスを書きました。
  * 実際は、時間や発信元の電話番号または発信者名などをファイル名とし、属性値で問い合わせフローに渡すことになると思います。
  * 再生用の問い合わせフローは以下のようになります。この問い合わせフローを、【一斉発信】のコードで呼び出すことで、電話に応答した時にS3に保存された音声が再生されます。![play_contactflow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1597092/519cca16-14d0-7b0e-4535-d7355b21bdd7.png)
  * 電話番号にこの問い合わせフローを割り当てると、折り返しされた時にふきこんだ音声を流すことができます。

# こんなことにも使えそう
防災が着想だったものの、以下のようなしくみにも使えるかも？と思います。
* 緊急連絡網（Amazon Lexを活用して返答も！）
* 自分の声でモーニングコール（自分ではなく、他の方でも！）
* 音声伝言板のようなもの（検索できるように何らかラベリングが必要か）


# 参考とした記事
大いに参考にいたしました。

* **留守電機能**

https://dev.classmethod.jp/articles/amazon-connect-voice-mail-from-kinesis-video-stream/

https://qiita.com/echolimitless/items/af068e147e233c51d6a4

* **再生可能な形式で保存**

https://dev.classmethod.jp/articles/ffmpeg-lambda-layer/

https://xp-cloud.jp/blog/2018/05/18/3345/

* **一斉発信**

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/connect.html#Connect.Client.start_outbound_voice_contact

* **録音した音声が再生される**

https://dev.classmethod.jp/articles/amazon-connect-dynamic-voice-playback-s3/

* **デモ動画内の「ピピっ」という音源**
OtoLogicさまの音源を利用しました。

https://otologic.jp