Asterisk で音声返答付きモーニングコール機能を実装する

2025/11/23 19:35 PerlLinuxAsterisk
Asterisk で運用している内線電話システムに、モーニングコール機能を実装しました。受付・キャンセル時に自動音声で応答します。

前提条件

  • Ubuntu 24.04.3 LTS
  • FFmpeg インストール済み (version 6.1.1-3ubuntu5)
  • Asterisk で通話可能になっている
  • 文字コードは UTF-8 で統一

準備1 音声を生成するためのもの (OpenJTalk + MMDAgent)

インストール

$ sudo apt install open-jtalk open-jtalk-mecab-naist-jdic hts-voice-nitech-jp-atr503-m001
$ : MMDAgent から音声モデルをいただきます
$ wget https://sourceforge.net/projects/mmdagent/files/MMDAgent_Example/MMDAgent_Example-1.8/MMDAgent_Example-1.8.zip
$ unzip MMDAgent_Example-1.8.zip
$ sudo mv MMDAgent_Example-1.8/Voice/mei /usr/share/hts-voice/
$ : 発声が出来るか確認
$ echo "10分" | open_jtalk -m /usr/share/hts-voice/mei/mei_normal.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow /tmp/voice.wav -g 15 && aplay /tmp/voice.wav

文字列を Asterisk 用音声に変換するスクリプトを用意

パスを /usr/local/bin/asterisk-utils/jtalk2wav.pl とします。実行権限を付加します。
標準状態の Asterisk で .wav ファイルを再生するには モノラル / サンプリング周波数8000Hz である必要があるようです。
#!/usr/bin/perl

use strict;
use warnings;

my $VOICE     = '/usr/share/hts-voice/mei/mei_normal.htsvoice';
my $DICT      = '/var/lib/mecab/dic/open-jtalk/naist-jdic';
my $OTHER_OPT = '';

my($text, $output_wav) = @ARGV;

my $TEMP_WAV  = "/var/tmp/$$.wav";

open(my $pipe, '|-', "/usr/bin/open_jtalk -m $VOICE -x $DICT -ow $TEMP_WAV $OTHER_OPT") or die;
print $pipe $text;
close $pipe;

system("ffmpeg -i $TEMP_WAV -af 'aformat=sample_rates=8000' -loglevel quiet -y $output_wav");
unlink($TEMP_WAV);

exit;

準備2 受付・キャンセル用スクリプトの用意

パスを /usr/local/bin/asterisk-utils/morning-call.pl とします。実行権限を付加します。
#!/usr/bin/perl -w

use v5.12;

use FindBin;
chdir $FindBin::Bin or die;

use Getopt::Long qw(:config posix_default no_ignore_case gnu_compat);


my $OUT_SPOOL = '/var/spool/asterisk/outgoing';
my $TEMP_DIR  = '/var/tmp';
my $SOUND_EXT = '279';  #モーニングコール音源につながる内線番号

my($action, $time, $caller, $answer_wav);
GetOptions('answer-wav=s' => \$answer_wav,
           'action=s'     => \$action,
           'time=s'       => \$time,
           'caller=s'     => \$caller,
                                        ) or die 'Invalid options';

my $answer_text = '';

if( $action =~ /REGISTER/i && $time =~ /^(\d{2})(\d{2})$/ && $1 <= 23 && $2 <= 59){
    my($h, $m) = ($1, $2);

    cancel($caller);

    my ($date, $today_or_tomorrow) = get_date_at_the_time($time);
    register($date, $time, $caller);

    $h =~ s/^0//;
    $m =  $m==0 ? 'ちょうど'
         :$m==30? '半'
         :( int($m). "分" );

    $answer_text = "モーニングコールを $today_or_tomorrowの $h時$mにセットしました";

}elsif( $action =~ /CANCEL/i ) {
    cancel($caller);
    $answer_text = "モーニングコールをキャンセルしました";

}else{
    $answer_text = "無効な指定です";
}

system("./jtalk2wav.pl '$answer_text' $answer_wav") if $answer_wav;
say $answer_text;

exit;



#コールをセット.
sub register {
    my($date, $time, $caller_ext) = @_;

    my $file = "$TEMP_DIR/morning-call-$caller_ext-$date-$time.txt";
    open(my $fh, '>', $file) or die("Cannot create $file");

    my $body = << "    EOM";
        Channel: PJSIP/$caller_ext
        CallerID: Good_Morning!!
        MaxRetries: 0
        RetryTime: 300
        WaitTime: 30
        Context: ipdenwa
        Extension: $SOUND_EXT
        Archive: yes
    EOM

    $body =~ s/^\s+//gm;
    print $fh $body;

    system("touch --date='$date $time' $file");
    system("mv $file $OUT_SPOOL");
}



#指定内線番号の登録をすべて解除.
sub cancel {
    my($caller_ext) = @_;

    unlink glob("$OUT_SPOOL/morning-call-$caller_ext-*.txt");
}



#指定された時刻に応じて ( 今日or明日の日付(ISO8601),  文字列「今日」or「明日」 ) をリストで返す.
sub get_date_at_the_time {
    #時刻は3~4桁の数値で指定
    my($time) = @_;

    my $date;
    my $today_or_tomorrow;

    my $now_time = int(`date +%H%M`);

    #現在時刻が 指定時刻を過ぎていたら
    if( $time <= $now_time ){
        $today_or_tomorrow = '明日';
        $date = `date --iso-8601 --date 1day`;
    }else{
        $today_or_tomorrow = '今日';
        $date = `date --iso-8601`;
    }

    chomp $date;
    return ($date, $today_or_tomorrow);
}

準備3 モーニングコール音源の用意

モーニングコールを受けた際に再生する音楽や音声などを、モノラル / サンプリング周波数8000Hz の .wav ファイルで用意します。
お好みで CD から音楽をリッピングしたり、前述の OpenJTalk で音声を合成したりしてください。
そのファイルを私は /var/lib/asterisk/sounds/asadayo.wav に置きました。

準備4 extensions.conf への追記

/etc/asterisk/extensions.conf にモーニングコールに関する動作を追記します。
私の環境では内線番号には200番台を振っています。それにならって、モーニングコールのセットには「277 + 時刻4桁」を、キャンセルには「278」を、またモーニングコール音源を再生する番号には「279」を割り当てました。
[house] は私の環境における内線のためのコンテキストです。適宜読み替えてください。
[house]

(...中略...)

; 277xxxx モーニングコール 登録
exten => _277XXXX,1,NoOp(REGISTER-MORNINGCALL)
same  => n,Answer
same  => n,Playback(beep)
same  => n,Set(ANSWER_WAV_BASENAME=/var/tmp/morning-call-answer-$CALLERID(num))
same  => n,System(/usr/bin/rm $ANSWER_WAV_BASENAME.wav)
same  => n,System(/usr/local/bin/asterisk-utils/morning-call.pl --action register --time '$EXTEN:3' --caller '$CALLERID(num)' --answer-wav $ANSWER_WAV_BASENAME.wav)
same  => n,Playback($ANSWER_WAV_BASENAME)
same  => n,System(/usr/bin/rm $ANSWER_WAV_BASENAME.wav)
same  => n,Wait(1)
same  => n,Playback(beep)
same  => n,Hangup

; 278 モーニングコール 取り消し
exten => _278,1,NoOp(CANCEL-MORNINGCALL)
same  => n,Answer
same  => n,Playback(beep)
same  => n,Set(ANSWER_WAV_BASENAME=/var/tmp/morning-call-answer-$CALLERID(num))
same  => n,System(/usr/bin/rm $ANSWER_WAV_BASENAME.wav)
same  => n,System(/usr/local/bin/asterisk-utils/morning-call.pl --action cancel --caller '$CALLERID(num)' --answer-wav $ANSWER_WAV_BASENAME.wav)
same  => n,Playback($ANSWER_WAV_BASENAME)
same  => n,System(/usr/bin/rm $ANSWER_WAV_BASENAME.wav)
same  => n,Wait(1)
same  => n,Playback(beep)
same  => n,Hangup

; 279 きみの朝
exten => 279,1,Answer
same  => n,Wait(1)
same  => n,Playback(/var/lib/asterisk/sounds/asadayo)
same  => n,Hangup
Asterisk を再起動後、SIP電話機から 279 へダイヤルし音源が再生されることを確認してください。場合によっては音源のボリュームを調整する必要もあるかもしれません。

使い方

セット

SIP電話機から「277」に続いて時刻を4桁でダイヤルします。例えば現在時刻が午後9時で明朝7時にモーニングコールをセットしたいときは、「2770700」とダイヤルします。
「モーニングコールを 明日の 7時ちょうどにセットしました。」と音声が流れ、電話が切られます。この電話機に翌朝7時にモーニングコールが掛かってきます。

キャンセル

SIP電話機から「278」にダイヤルします。
「モーニングコールをキャンセルしました。」と音声が流れ、電話が切られます。

参考