【Twilio】AMDで留守番電話を検知する
IT技術
はじめに
入社2年目のみやです。未経験新卒で入社して1年が経ち、他のメンバーのサポートもありプロジェクトの一員としてサービスの開発保守に取り組むことができています。今回は最近実装したTwilioのAMDという留守番電話機能を用いた留守番電話の検知に関してアウトプットも兼ねてご紹介いたします。
前提
- Twilioを用いて、電話をかける方法を知っていることを前提に説明します
- コードはphpでの紹介になります(他の言語を使っている方にも参考になると思います)
留守番電話検知の必要性
Twilioを用いることで、プログラムで自動で電話をかけることができます。
電話をかけた相手が電話に出た時と出なかった時で後続の処理を変えたい場合があります。
この時問題になるのが、何も特別な設定をしないと留守番電話が応答(「電話に出ることができません。発信音の後に…」のようなメッセージが流れるあれです)したときと人間が応答した時の判別がつかないことです。
一般的には人間が応答した時と留守番電話が応答した時の処理は分けたいことが多いと思います。
このように人間が応答した時と留守番電話が応答した時を判別したい時に考えられる対処法が主に2つあります。
-
- "Human Detection"(人間検知)
応答したら応答者に通話を繋ぐためにキーパットから数字を入力するようお願いするメッセージを流し、入力された数字により判断する方法です。
人間が応答した場合は数字が入力されますが、留守番電話が応答した場合は数字が押されないので判別することができます。
-
- "Answering Machine Detection"(留守番電話検知)
Twilioが提供しているAMD(Answering Machine Detection 以後AMD)という留守番電話検出機能を用いて、機械的に応答者が人間か留守番電話かを判断する方法です。
AMDによって、応答者の声のトーンや発話パターンから自動的に判断できます。
どちらの対処方法にも一長一短があるので、以下に個人的な考えをまとめました。
メリット | デメリット | |
---|---|---|
人間検知 | 確実に判定できる。 間違えることがない。 | 応答者の手間が増える。 留守番電話検知より通話するまで時間がかかる (「数字を入力してください。...」などの やりとりがあるため)。 |
留守番電話検知 | 応答者の手間は増えない。 即座に通話できる。 | 判定を誤る可能性がある。 (料金がかかる。) |
開発しているサービスの性質上、AMDを導入することになりましたので、本記事では留守番電話検知の方を紹介します。
本記事では詳しく説明はしませんが、人間検知の方はSay動詞(公式)で数字を入力するようにメッセージを流し、Gather動詞(公式)を用いて、入力された数字を収集して分岐させるイメージです。
簡単にコード例を以下に記載しました。
1<?php
2use Twilio\Rest\Client;
3use Twilio\TwiML\VoiceResponse;
4
5$accountSid = 'XXX';// twilioのAccount SID
6$authToken = 'XXX'; // twilioのAuth Token
7
8$twilio = new Client($accountSid, $authToken);
9
10$url = 'XXX';// 入力結果を送信するURL
11$response = new VoiceResponse();
12$gather = $response->gather(['action' => $url]);
13$gather->say('通話をお繋ぎします。数字の1を入力してください。', ['language' => 'ja-JP']);
14$response->say('数字が入力されませんでした。通話を終了します。', ['language' => 'ja-JP']);
15
16$receiverNum = 'XXX';// 発信先電話番号
17$senderNum = 'XXX';// 発信元電話番号
18$twilio->calls->create($receiverNum,$senderNum,["twiml" => $response]);
上記コードの説明
発信先が電話に出ると、「通話をお繋ぎします。...」のメッセージが流れます。メッセージに応じて応答者が数字を入力すると、入力された内容とともに$url
にリクエストが送られます。
リクエスト先でリクエストパラメータ"Digits"
から入力された数字を受け取り、受け取った数字に応じて新たなメッセージを流すなど処理を分岐させることができます。
数字が入力されないと、「数字が入力されませんでした。...」のメッセージが流れ、電話が切断するようにしているので、留守番電話が応答した時は自動で電話が終了することができます。
簡単ですが以上が人間検知についての説明でした。では、ここから本題のAMDの説明に入ります。
サービスの内容によっては、人間検知の方が有用になる場合があると思いますので、適宜判断してください。
AMD
前述した通り、AMD(Answering Machine Detection: 公式)はTwilioが提供している留守番電話検出機能です。
公式によると、AMDは以下の2つの側面から成り立っているようです。
-
- "tone detection"(トーン検出)
ビープ音やビジートーン(話中音)、ファックス音、トーン信号などを検出する
-
- "voice activity detection / human speech detection"(発話区間検出 / 人間発音検出)
人間の発話を周りのノイズから切り離す。また、タイミング、パターン、周波数の情報を分析して検出する。
これらの能力を用いて、応答者の音声を自動的に判別してくれているようです。
AMDを導入するにあたって注意すべき点
AMDを導入する際にいくつか気をつけなければならない点があります。
1. 料金がかかる
AMDを有効にした通話1回あたり、 $.0075の追加料金が発生します(2024/4/16時点の公式参照)。ただし、通話がつながった場合(留守番電話が応答しても)のみ料金が発生するそうです。2024/4/16時点で$.0075は1.16円です。
2. 判定の正確性は100%ではない
公式発表によるとデフォルトの設定だと、正確性は約90%です(公式)。
(3. 判定に少し時間がかかる)
応答者が電話に出てから、平均して4秒以内に判定結果を返します(公式)。開発時に触っていた経験からしても、4秒以内では結果が返ってくる印象です。括弧をつけたのは、非同期的にAMDを判定することができるので、この点は無視することができるからです(後述します)。
AMD導入実装のポイント
ある通話に対して、AMDを有効にしたい場合は、Calls APIのパラメータにMachineDetection=>"Enable"
と設定して、通話を作成するだけです。この設定だけで、その通話に対してAMD判定を行ってくれます。
AMD判定に関して、主に2つの実装方法がありますので順に紹介していきます。
1. 同期的にAMD判定を行う
AMDの判定結果が出るまで次の動作が行われないような実装例です。
1<?php
2use Twilio\Rest\Client;
3use Twilio\TwiML\VoiceResponse;
4
5$accountSid = 'XXX';// twilioのAccount SID
6$authToken = 'XXX'; // twilioのAuth Token
7
8$twilio = new Client($accountSid, $authToken);
9
10$url = 'XXX';// 入力結果を送信するURL
11$receiverNum = 'XXX';// 発信先電話番号
12$senderNum = 'XXX';// 発信元電話番号
13$twilio->calls->create($receiverNum,$senderNum,["machineDetection" => "Enable", "url" => $url]);
上記コードの説明
発信先が電話に出ると、応答者の音声からAMDが判定を行います。判定の間、通話は止まっています。判定が終わると、判定結果とともに$url
にリクエストが送られます。
リクエスト先でリクエストパラメータ"AnsweredBy"
から判定結果を受け取り、判定結果に応じて後続の処理を分岐させることができます。
次に、Enqueue動詞(公式)を使って、電話に応答した場合自動音声を流し、応答者に待ってもらうパターンを考えます。
1<?php
2use Twilio\Rest\Client;
3use Twilio\TwiML\VoiceResponse;
4
5$accountSid = 'XXX';// twilioのAccount SID
6$authToken = 'XXX'; // twilioのAuth Token
7
8$twilio = new Client($accountSid, $authToken);
9
10$waitUrl = 'XXX';// 待機時のメッセージを流すURL
11$twiml = new VoiceResponse();
12$twiml->enqueue("support", [
13 'waitUrl' => $waitUrl,
14 ]);
15
16$url = 'XXX';// 入力結果を送信するURL
17$receiverNum = 'XXX';// 発信先電話番号
18$senderNum = 'XXX';// 発信元電話番号
19$twilio->calls->create($receiverNum,$senderNum,["machineDetection" => "Enable", "url" => $url, "twiml" => $twiml]);
$waitUrlでの処理
1<?php
2use Twilio\TwiML\VoiceResponse;
3
4$waitMessage = new VoiceResponse();
5$waitMessage->say("電話をお繋ぎします。少々お待ちください。", ['language' => 'ja-JP']);
6
7return $waitMessage
上記コードの説明
上記のコードだと、AMDの判定が出る間、応答者に対してメッセージを流すことができません。なぜなら、AMDの判定が出るまでの間通話の実行はブロックされるからです。判定が出ると、$urlにリクエストされるので、上記コードだと$waitUrlが意味をなしません。
このように、AMDの判定を待っている間、応答者にメッセージや保留音を流したい場合があると思います。この時に便利なのが次に紹介する非同期的なAMDの実行です。
2. 非同期的にAMD判定を行う
AMDを非同期的に実行するには、Calls APIのパラメータにasyncAmd=>true
の設定を追加するだけです。これにより、バックグラウンドでAMDの判定を行いながら通話を継続することができます。また、先ほどと違いAMDの判定結果を送るエンドポイントはパラメータasyncAmdStatusCallback
で指定する必要があります。
では、先ほどと同様にEnqueue動詞(公式)を使って、電話に応答した場合自動音声を流し、応答者に待ってもらうパターンを考えます。
1<?php
2use Twilio\Rest\Client;
3use Twilio\TwiML\VoiceResponse;
4
5$accountSid = 'XXX';// twilioのAccount SID
6$authToken = 'XXX'; // twilioのAuth Token
7
8$twilio = new Client($accountSid, $authToken);
9
10$waitUrl = 'XXX';// 待機時のメッセージを流すURL
11$twiml = new VoiceResponse();
12$twiml->enqueue("support", [
13 'waitUrl' => $waitUrl,
14 ]);
15
16$asyncAmdStatusCallback = 'XXX';// 入力結果を送信するURL
17$receiverNum = 'XXX';// 発信先電話番号
18$senderNum = 'XXX';// 発信元電話番号
19$twilio->calls->create($receiverNum,$senderNum,["machineDetection" => "Enable", "asyncAmd" => true, "asyncAmdStatusCallback" => $asyncAmdStatusCallback, "twiml" => $twiml]);
※$waitUrlでの処理は先ほどと同じ
上記コードの説明
発信先が電話に出ると、通話がキューに入り、「電話をお繋ぎします。...」の保留中のメッセージを流すことができます。その間、AMDはバックグラウンドで応答者の反応から判別を行います。判別が終わると、$asyncAmdStatusCallbackに判定結果とともにリクエストを送ります。リクエスト先でリクエストパラメータ"AnsweredBy"
から判定結果を受け取り、判定結果に応じて後続の処理を分岐させることができます。
AMDの判定結果について
AMDが判定の結果として返すAnsweredBy
の値は以下の4つがあります。
-
- "human"
人間が応答したと判定した場合
-
- "machine_start"
機械が応答したと判定した場合
-
- "fax"
ファックスが判定したと判定した場合
-
- "unknown"
AMDが判定を試みたが、判別がつかなかった場合
次に紹介するAMDを使う際に設定できる任意のパラメータが、判定のミスや"unknown"と判定される頻度を左右します。
AMD導入時の任意のパラメータ
これまでに説明したAMD導入に設定が必要なパラメータMachineDetection
、(asyncAmd
)、(asyncAmdStatusCallback
)以外にも任意に設定できるパラメータが存在します。
以下は公式を参考にまとめたものです。
-
- "MachineDetectionTimeout"
TwiloがAMDの判断を試みてから、タイムアウトしてAnsweredBy
が"unkown"として返されるまでの秒数。デフォルト値は30秒。デフォルト値は長いように思えますが、デフォルト値のまま使っても問題ありませんでした。短くすると、"unknown"と判定される頻度が高くなるようですが、私の場合30秒も判定にかかったことはありません。
-
- "MachineDetectionSpeechThreshold"
発話時間の長さを表すミリ秒数。この値より短い場合は人間として、長い場合は機械として解釈される。デフォルト値は2400ミリ秒。
この値を大きくすると、長い発話に対するFalse Machine(実際は人間だが機械と判断)の可能性は低くなるが、機械と判断するのに時間がかかる。
この値を小さくすると、短い発話に対するFalse Human(実際は機械だが人間と判断)の可能性は低くなるが、False Machineの可能性が高くなる。
-
- "MachineDetectionSpeechEndThreshold"
発話終了後、発話が完了したとみなされる無音のミリ秒数。デフォルト値は1200ミリ秒。
この値を大きくすると、短い留守番電話メッセージに対応しやすくなる。
この値を小さくすると、人間と判断するのが速くなる。False Human(実際は機械だが人間と判断)の可能性は高くなる。
-
- "MachineDetectionSilenceTimeout"
AnsweredBy
が"unkown"として返されるまでの最初の無音のミリ秒。デフォルト値は5000ミリ秒。
例えば、応答者が「もしもし、」などの言葉を発しないで、無言で応答した場合に、それが何秒続いた場合に"unknown"と返すかを決定する値。
上記のように細かく設定できるようですが、私は全てデフォルト値のままで問題なく実装・動作の確認まで行えました。よりシビアな判定が必要な方は、上記のパラメータをいじる必要があるかと思います。
AMD導入Tips
AMDを導入する中で、Twilioサポートチームとやり取りをしたり実装を試行錯誤したりしたので、その中で分かったTipsを紹介します。
-
- 最初はデフォルトの設定でテストする
前述した通り、任意のパラメータを色々設定することはできます。ただ、デフォルトの設定は最短時間で正確な判断ができるようになっているようなので、まずデフォルトの設定で動作の確認をした方が良いです。
-
- テスト時の発声
AMDのテスト時は応答時に何度も「もしもし」や「こんにちは」と発声するのではなく、「もしもし」や「こんにちは」などを一言だけ発生し、テストする。
最後に
今回はTwilioが提供しているAMD(Answering Machine Detection 以後AMD)という留守番電話検出機能について、簡単なコード例も交えて紹介しました。詳しく書いたつもりですが、全てを網羅できているわけではありません。例えば、留守番電話が応答した場合に自動音声でメッセージを残すような実装もできるようです。AMD以外にもTwilioのサービスについて知らない部分がまだまだあるので、今後学習できればと思います。この記事がTwilioでAMDの導入を考えている方の参考になれば幸いです。
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
社会人一年目のみやです。大学時代はエンジニアとは無縁の生活。ひょんな事からこの業界に興味を持ち、エンジニアとしてライトコードで働いています。未経験ですが、どんどん吸収して成長していきます!
おすすめ記事
immichを知ってほしい
2024.10.31