令和にもなって VoIP 電話網を作ろうとしているお前らへ


注意
このスクラップの内容は、後に修正・更新されて記事として投稿されるかもしれません。
投稿者へのお願い
このスクラップの内容を記事にする場合、元となったスクラップの投稿を削除しないでください。 該当するスクラップの冒頭部分に message
構文で記事として公開された旨を記述し、スクラップのうち記事として公開された部分を details
構文で折り畳むか、あるいはスクラップそのものを非表示にしてください。 元の投稿を削除した場合、その投稿を参照していた読者がいた場合に混乱を招きます。
<!-- 以下を追加する -->
:::message
このスクラップは記事[○○の解説](https://example.com/hogehoge)として公開されました。
:::
<!-- details で折り畳むには以下のように記述する -->
:::details もともとのスクラップの内容
<!-- ここにスクラップの内容 -->
:::

カンファレンスルームや Echo-back service などを外線から受け付けられるようにする
-
カンファレンスルームについては、作成時に内線番号 / 直通番号 (DID) を割り当てるため、着信ルールでこれを受け付けるようにします。
-
Echo-back service などのアプリケーションを受け付けるには、着信ルールでどの内線番号 / 直通番号 (DID) を受け取ったらそのアプリケーションへルーティングするかを定義します。
優先度設定において、上記の例外的な番号をまずフィルターするようにし、最後に、リアルな宛先 (ユーザー) が存在する内線番号 / 直通番号 (DID) へルーティングするようにします。
KusaReMKN 氏の環境では、直通番号へルーティングするためのルールを新しく作成していますが、以下のように「着信デフォルトルート」として定義することもできます。

Linux 用 FAX ユーティリティ
ハードウェアモデムなどが接続されている場合、efax が便利です。GUI のフロントエンドを持つ efax-gtk はもっと便利です。これは USB モデムの上で動作することを確認しました。シリアルケーブル越しの物理外付けモデムではうまく動かないようです(たぶんコマンドが異なる?)

Discord の音声チャンネルと MikoPBX (Asterisk) のカンファレンスをブリッジする
用意するもの
- Microsoft Windows の VM
- 検証環境では Proxmox VE 上に Windows の VM を用意しています
- MikoPBX
- ブリッジに使用するカンファレンス
- ブリッジに使用する内線番号 (内線アカウント)
- あなたが管理者権限を持っている Discord サーバ
- このブリッジに使用する音声チャンネルを事前に作成してください
- Discord ボットのトークン
- https://discord.com/developers から取得できます
- Discord ボットは、ブリッジに使用する音声チャンネルが作成されている Discord サーバに参加させてください
手順
- Windows に以下のアプリケーションをインストールします
- VB-Audio Virtual Audio Cable
- VB-Audio VoiceMeeter
- Discord Audio Stream Bot
- https://github.com/BinkanSalaryman/Discord-Audio-Stream-Bot
-
%userprofile%
以下など、適当な場所にリリース アーティファクトDiscord.Audio.Stream.Bot.zip
を解凍してください
- MicroSIP
VB-Audio Virtual Audio Cable, VB-Audio VoiceMeeter 両方のインストールが完了したら、一度 Windows を再起動してください。
- Discord Audio Stream Bot を設定する
Discord Audio Stream Bot は、Windows のサウンド デバイスを介して他のアプリケーションと音声を送受信し、Discord の音声チャンネルにブリッジするものです。
- Settings タブ
- General
- Bot token: Discord から取得したボットのトークンを入力します
- Auto login: オン
- Audio
- Mute/Unmute: マイクとスピーカーが、どちらもミュートされていない状態にします。
- Input device:
VoiceMeeter Output (VB-Audio Vo
- Output device:
CABLE Input (VB-Audio Virtual Cable)
- Voice activity: オフ
- Speak threshold: 変更しない (この値は使用されない)
- General
- MicroSIP を設定する
MicroSIP は、Windows 向け SIP (VoIP) クライアント アプリケーションです。
- 設定
- スピーカー:
VoiceMeeter Input (VB-Audio Voi
- マイク:
Cable Output (VB-Audio Virtual
- システム起動時に実行: オン
- スピーカー:
- アカウントの追加
- アカウント名: ブリッジに使用する内線番号 (内線アカウント)
- SIP サーバ: MikoPBX の IP アドレス / ホスト名
- ユーザ名: ブリッジに使用する内線番号 (内線アカウント)
- ドメイン: MikoPBX の IP アドレス / ホスト名
- ログイン: ブリッジに使用する内線番号 (内線アカウント)
- パスワード: ブリッジに使用する内線番号 (内線アカウント) のパスワード
- MikoPBX からカンファレンスの番号をコールする
- Discord Audio Stream Bot を起動する
- Home タブの 電源 ボタンを押下する。
- Discord サーバ内で、以下のボット コマンドを実行する
/autojoin <op: set> <channel: ブリッジに使用する音声チャンネル>
/join <channel: ブリッジに使用する音声チャンネル> <public: True>

DIALSの卵
概要
このダイアルプランアプリケーションのコードは、MikoPBX環境で通話中にユーザーが入力したDTMF(プッシュボタンの数字)を受け取り、「**#」が押されたら終了し、入力内容を一桁ずつ音声で再生する処理を行います。音声が再生されない場合は、MikoPBX環境にて音声ファイルが適切に配置されているか確認してください。
コード
<?php
require_once 'Globals.php';
use \MikoPBX\Core\Asterisk\AGI;
$agi = new AGI();
$agi->answer();
$agi->exec('Playback', 'beep');
// DTMF入力を待機(タイムアウト: 10秒)
$digits = '';
$end_sequence = '**#'; // 終了シーケンス
$end_sequence_length = strlen($end_sequence);
while (true) {
$result = $agi->wait_for_digit(-1); // -1 無制限に繰り返し
if ($result['result'] == -1) {
// タイムアウトまたはエラー
$agi->verbose("エラーが発生");
break;
} else {
// 入力されたキーASCIIで取得
$digit = chr($result['result']);
$agi->verbose("押したキー: $digits");
// 入力されたキーを保存
$digits .= $digit;
// 保存された入力が終了シーケンスで終わるか確認
if (substr($digits, -$end_sequence_length) === $end_sequence) {
$agi->verbose("終了シーケンス '$end_sequence' が入力されました。");
// 終了シーケンスが入力された場合、ループを終了
break;
}
}
}
// 入力されたDTMF全体をログに記録
$agi->verbose("DTMF: $digits");
// ここに計算するようにしてほしいな
// もし、外部のコードを実行するなら
// $agi->exec("AGI", '外部コードパス,$digits');
// でできるはず(わからない時は聞いて一緒に考えるよ)。
// 入力された各DTMFを一桁ずつ再生
for ($i = 0; $i < strlen($digits); $i++) {
$digit = $digits[$i];
if ($digit == '#') {
$agi->exec("Playback", 'digits/pound');
} elseif ($digit == '*') {
$agi->exec("Playback", 'digits/star');
} else {
$agi->exec('SayNumber', $digit);
}
}
// 通話を終了
$agi->hangup();
?>
その他
MikoPBX環境では、デフォルトで「**」が内部転送の処理呼び出しに使用されています。そのため、「**#」と競合し、終了操作を行おうとすると転送が実行されてしまいます。この問題を回避するためには、MikoPBXの設定を変更する必要があります。WEB UIの「General settings」にある「Call Transfers」セクションで、以下の2つの設定を変更してください。(これ以外もあるかもしれません)
- 「This key combination activates an attended transfer」(アテンド転送を起動するキー)
- 「This key combination activates a blind transfer」(ブラインド転送を起動するキー)
設定例として、「####」や「##**」などのキー組み合わせを指定できます。以下に設定画面の例を示します。
参考

IPv4 / IPv6 両方で SIP ポートをリッスンする
MikoPBX の既定の設定では、IPv4 のみがリッスンされています。先進的な考えを持つ方が IPv6 上で MikoPBX を利用するには、以下のようにします。
-
/etc/asterisk/pjsip.conf
を「ファイルの最後に追加」モードで「システムファイルのカスタマイズ」を行います。 - 「変更を加える編集者」欄に以下を追記します。
[transport-udp-v6]
type = transport
protocol = udp
bind=[::]:5060
[transport-tcp-v6]
type = transport
protocol = tcp
bind=[::]:5060

発信者番号を通知する
- 着信用のテレフォニープロバイダーに、以下の 追加オプション を追記します
[endpoint]
trust_id_inbound=yes
- 発信用のテレフォニープロバイダーに、以下の 追加オプション を追記します
- こちらに関しては、テレフォニープロバイダーが複数存在することが想定されますが、そのすべてに追記します。
- 発信者番号を意図的に秘匿したい場合、特定のテレフォニープロバイダーに追記しないこともできます。
[endpoint]
send_pai=yes
trust_id_outbound=yes

MikoPBX のダイヤルプランアプリケーションで Fax を受信する
着信用テレフォニープロバイダーの修正
テレフォニープロバイダー from-any の Advanced Options に以下を追加します。既に設定が存在する場合はよしなにしてください。
[endpoint]
fax_detect=yes
fax_detect_timeout=30
Fax 関連モジュールの追加
システムファイルのカスタマイズから、/etc/asterisk/modules.conf
の「ファイルの最後に追加」します
load => res_fax.so
load => res_fax_spandsp.so
ダイヤルプラン アプリケーションを追加
コードタイプが PHP-AGI スクリプト のダイヤルプラン アプリケーションを作成します。
ソースコードは以下に示します。
<?php
require_once 'Globals.php';
use \MikoPBX\Core\Asterisk\AGI;
$agi = new AGI();
$faxFile = "/tmp/" .$agi->get_variable("CDR(linkedid)", true).'.tiff'; // 一時ファイル
$caller = $agi->get_variable("CALLERID(num)", true); // 発信者番号
// 保存先ディレクトリ
$saveDir = "/mikopbx/fax_files/";
if (!file_exists($saveDir)) {
mkdir($saveDir, 0777, true); // 保存ディレクトリがない場合は作成
}
// FAX受信の実行
$agi->exec("ReceiveFax", "{$faxFile},d");
$result = $agi->get_variable("FAXOPT(status)", true); // 受信結果を取得
if ($result === 'SUCCESS' && file_exists($faxFile)) {
// 保存用ファイル名を設定
$savedFile = $saveDir . date("Ymd_His") . "_from_{$caller}.tiff";
// 一時ファイルを保存先に移動
rename($faxFile, $savedFile);
$agi->verbose("FAX successfully saved to {$savedFile}");
} else {
$agi->verbose("FAX reception failed or file not found.");
}
sleep(1);
-
compose.yaml
の編集
compose.yaml
を編集し、受信した Fax のデータを永続化するようにします。
上記のソースコードでは、コンテナ内のパスであるところの/mikopbx/fax_files
に保存するようにしていますから、これを Docker Engine を稼働させているホスト側に volumes で露出させてください。

MikoPBX と SIP クライアントとが同一のホスト上にあると正常に動作しない
標記の通りです。別のホストで動作させましょう。
テスト番号の実装方法
- 再生させたい音声を録音
- MikoPBXへ音声をアップロード
- 左メニュー「サウンドファイル」を選択
- 「新しいサウンドファイルを追加」からファイルをアップロード
- アップロードしたファイルパスをメモ帳などに控える
- 確認方法はアップロードしたファイルの右側アイコン編集を開く
- ダイヤルプランアプリケーションの追加
- 「モジュール」項目の「ダイヤルプランアプリケーション」を開く
- アプリケーション設定を下記のように設定
- プログラムコードを下記のように記述。このときに先ほど控えたファイルパスを記述。※拡張子は不要
アプリケーション設定
- タイトル:好きなものを入力,
- 解説:好きなものを入力,
- アプリケーション呼出番号:音声を流したい内線番号を入力。(東京広域電話網では1234が多い),
- コードタイプ:Dialplanアスタリスク,
プログラムコード
1,Answer()
n,Playback(/storage/usbdisk1/mikopbx/media/custom/2e6c16e41a101c47f86d349eb3227686-20240110)
n,goto(2)
※2行目のカッコ内はご自身のファイルパス・ファイル名に変更してください。
※3行目はループ再生用なので、1度の再生のみにしたい場合は3行目は消してください。
アドバイスしていただいた8yazakiさんに感謝。