Twilio録音データを用いたユーザー発話への音声処理

こんにちは、AIチームの杉山です。今回はTwilio録音データを用いたユーザー発話への音声処理の方法を紹介します。

Twilio

TwilioはWeb API経由で電話の発着信などを行うことのできるサービスです。
こちらのサービスが電話の世界をラップしてくれるおかげで、私のような一般的なWebエンジニアでもこちらの作法で電話による音声自動応答サービスを簡単に開発することができます。
とはいえ音声データの取り扱いに馴染みのない方もいるかと思いますので、今回はTwilioの通話録音データからユーザーの発話箇所を切り出し、音声処理をかけるまでの手順を紹介したいと思います。
また次回以降、ストリーミング接続でリアルタイムに流れてくる音声チャンクを用いた音声信号処理についても紹介できればと思います。

録音コールバックの設定

以降、実装の説明はPython+Twilio clientを用いて行いますが、本題と関係のない箇所の詳細は省力します。

pip install twilio

Twilioでは通話終了時に録音処理が行われ、これにより通話データがTwilioのサーバーに保存されます。その際、以下のような設定により、保存完了をトリガーに任意のwebhookを発火させることができます。

from twilio.rest import Client

client = Client(f"{YOUR_ACCOUNT_SID}", f"{YORU_AUTH_TOKEN}")
recording_callback_url = f"{YOUR_RECORDING_CALLBACK_ENDPOINT}"
client.calls(call_sid).recordings.create(
    recording_status_callback=recording_callback_url,
    recording_status_callback_method="POST",
    recording_channels="dual",
)

recording_channelsにdualを指定することでシステム側の音声とユーザー側の音声を別のチャネルとして保存するため後述の処理のためにそちらを指定します。
webhook側で、例えばTwilioに保存されたデータを自分たちで管理する任意のストレージにコピーしたり、録音データに対して後処理的に音声信号処理を適用することが考えられます。

録音データの取得と分割

まずはTwilioに保存された録音データをダウンロードします。録音データはwebhookへのリクエストパラメータとして与えられるRECORDING_SIDというIDで管理されているためそちらを用いてURIを指定します。

import base64
import urllib

recording_url = f"https://api.twilio.com/2010-04-01/Accounts/{YOUR_ACCOUNT_SID}/Recordings/{RECORDING_SID}"

# basic認証の設定
basic_auth = base64.b64encode(
    f"{YOUR_ACCOUNT_SID}:{YOUR_AUTH_TOKEN}".encode("utf-8")
)
headers = {
    "Authorization": "Basic " + basic_auth.decode("utf-8")
}

recording_request = urllib.request.Request(
    url=recording_url, headers=headers
)
# 録音データの取得
with urllib.request.urlopen(recording_request) as wd:
    recording_data = wd.read()

録音データが取得できたら、そちらを音声データとして読み込みます。今回はシステム側の発話は用いず、ユーザーの発話箇所に対して音声信号処理を適用するケースを考えます。先ほどdualチャネルを指定し、それぞれの音声が別のチャネルとして交互に保持されているのでスライスでユーザー側の音声である偶数番目のデータだけを取り出します。

import wave
import numpy as np

# 音声データとして読み込み
with wave.open(io.BytesIO(recording_data), mode="rb") as wf:
    buf = wf.readframes(-1)  
data = np.frombuffer(buf, dtype="int16")
user_data = data[::2]  # ユーザー側の音声だけを取得

これでユーザー側の音声だけを取得できましたが、こちらは通話開始から終了までの全期間が含まれます。音声認識や音声感情分類などの処理を行いたい場合は発話単位のデータが必要になると考えられるため、発話区間ごとに音声を区切りたいと思います。
音声区間検出(Voice Activity Detection; VAD)により発話箇所ごとに区切る方法も考えられますが、今回は通話時点でユーザーとシステムの発話開始タイムスタンプをそれぞれ記録していたものとします。ここではユーザーの発話開始時点からシステムの発話開始時点をユーザーの発話区間とみなし、その区間の音声切り出しを行います。Twilioは電話による音声通信サービスであるため、サンプリング周波数は8kHzになります。つまり、8000個のデータが1秒分に該当するため通話開始時点から数えてx秒目からy秒目まで切り出したい場合は以下のように実装できます。

cut_start_seconds = 切り出しを開始したい秒数x
cut_end_seconds = 切り出しを終了したい秒数y
cut_audio = user_data[
    (cut_start_seconds * 8000) : (cut_end_seconds * 8000)
]

これで、ユーザーの発話ごとの音声データを音声認識や感情認識など任意の処理を行う機能やAPIに渡すことができるようになりました。

終わりに

本記事ではTwilioの録音データに対してユーザーの発話箇所を切り出す方法を紹介しました。音声データの扱いに馴染みのない方に、こちらの記事が少しでも参考になれば幸いです。
なお今回は終了した通話の録音データに対して後処理的に処理を行う方法を紹介しましたが、実際のニーズとしては通話中にリアルタイムにユーザーの発話音声を取得し、音声認識などの処理を行いたいケースも多いと思います。
次回以降、そちらの方法についても紹介していきたいと思いますのでよろしくお願いいたします。

参考

https://www.twilio.com/docs/voice/api/call-resource#recordingstatuscallback

PICK UP

TAG