IVR 最初熟悉可能是移动,电信,联通三大运营商的手机电话,拨打10086等号码,客服端会提示你:按键1,查询本机花费,按键2,人工客服,按键0,重听一遍,请用#号键确认。
很简单,这就是一个IVR的典型应用。
最近几年,实时语音识别的普及,IVR中又加入了音频识别,用户不再需要通过按键的形式,直接通过发音表达的方式就可以实现原来按键的功能。

1、流程

学习任何一个框架,必须知道软件框架的底层逻辑是什么样的,代码执行流程是什么样的。熟悉了核心执行流程,系统整个优势,功能,包括按需开发真的是一目了然。要不然,在程序出现BUG,或者有新的需求的时候,就会感觉到无从下手。

我们以 5000 拨号,为例:

查找路由表:vi /user/local/freeswitch/conf/dialplan/default.xml

freeswitch注册分机 freeswitch常用命令_freeswitch注册分机


我们看到 action 在响应answer后,sleep了2秒钟,然后执行了ivr的app,传参为demo_ivr 菜单

所有ivr的相关demo文件,都在/user/local/freeswitch/conf/ivr_menus目录下

freeswitch注册分机 freeswitch常用命令_git_02


我们打开demo_ivr.xml, 找到namedemo_ivrmenu,如下:

<menu name="demo_ivr"
      greet-long="phrase:demo_ivr_main_menu"
      greet-short="phrase:demo_ivr_main_menu_short"
      invalid-sound="ivr/ivr-that_was_an_invalid_entry.wav"
      exit-sound="voicemail/vm-goodbye.wav"
      confirm-macro=""
      confirm-key=""
      tts-engine="flite"
      tts-voice="rms"
      confirm-attempts="3"
      timeout="10000"
      inter-digit-timeout="2000"
      max-failures="3"
      max-timeouts="3"
      digit-len="4">

    <!-- The following are the definitions for the digits the user dials -->
    <!-- Digit 1 transfer caller to the public FreeSWITCH conference -->
    <entry action="menu-exec-app" digits="1" param="bridge sofia/$${domain}/888@conference.freeswitch.org"/>
    <entry action="menu-exec-app" digits="2" param="transfer 9196 XML default"/>    <!-- FS echo -->
    <entry action="menu-exec-app" digits="3" param="transfer 9664 XML default"/>    <!-- MOH -->
    <entry action="menu-exec-app" digits="4" param="transfer 9191 XML default"/>    <!-- ClueCon -->
    <entry action="menu-exec-app" digits="5" param="transfer 1234*256 enum"/>       <!-- Screaming monkeys -->
    <entry action="menu-sub" digits="6" param="demo_ivr_submenu"/>                  <!-- demo sub menu -->
    <!-- Using a regex in the digits tag lets you define a dial pattern for the caller
         You may define multiple regexes if you need a different pattern for some reason -->
    <entry action="menu-exec-app" digits="/^(10[01][0-9])$/" param="transfer $1 XML features"/>
    <entry action="menu-top" digits="9"/>          <!-- Repeat this menu -->
  </menu>

其实从上述代码就可以看到,实现ivr的拨号功能的模块就是menu的子模块entry。参数digits代表拨号按键,action 表示执行appparam是参数。这儿有点像

<action application="bridge" data="user/1005" />

接下来,详细解析下menu节点参数和entry节点参数:

1.1 Menu-name

这儿name是拨号路由表dialplan中,ivr app 引用菜单传递的data参数值。它与文件名字无关,只和文件内的标签有关。

1.2 Menu-greet-long

传递给呼叫者的第一条信息内容,一般语音提醒用户:公司名称(您好,我们是xxx公司xxxx,)+ 操作步骤(按键1,xxx, 按键2, xxx)等。

我们看到该字段内容分两部分:
phrase:demo_ivr_main_menu 这是一个短语宏,格式是:phrase:macroname[:one or more arguments],冒号前面是phrase,代表短语宏;冒号后面macroname是短语宏名称,再如果还有冒号,后面所有内容就表示宏参数。

短语宏,是一种Freeswitch配置的XML结构,将一些简单的声音文件进行组合,配置成复杂的提示音。比如:库中只有简单的‘我’ ,‘和‘, ’你‘的三段音频文件,那么就可以通过配置每个字的音速,将三段音频合成一个完成的’我和你‘的音频。

短语宏配置文件:/usr/local/freeswitch/conf/lang 下 ,该文件夹放置支持的不同的语言

freeswitch注册分机 freeswitch常用命令_freeswitch注册分机_03


当然,freeswitch在初次加载的时候就已经加载进来了,

vi /user/local/freeswitch/conf/freeswitch.xml

freeswitch注册分机 freeswitch常用命令_路由表_04


每个目录下,都打开*.xmldemo/*.xml文件,看看

我们找到demo_ivr_main_menu短语宏macro 名称nameen/demo/demo-ivr.xml文件中

<macro name="demo_ivr_main_menu" pause="100"> <!-- See conf/autoload_config/ivr.conf.xml for an example on how to use this macro in an IVR -->
    <input pattern="(.*)">
      <match>
        <!-- string together several existing sound files to create one long greeting -->
        <action function="play-file" data="ivr/ivr-welcome_to_freeswitch.wav"/>
        <action function="play-file" data="ivr/ivr-this_ivr_will_let_you_test_features.wav"/>
        <action function="play-file" data="ivr/ivr-you_may_exit_by_hanging_up.wav"/>
        <!-- note that you can do more than just play files, e.g. have pauses and do TTS -->

        <!-- Menu option 1: Call FreeSWITCH conference-->
        <action function="play-file" data="ivr/ivr-enter_ext_pound.wav"/>
        <action function="play-file" data="silence_stream://1500"/>
        <action function="play-file" data="ivr/ivr-to_call_the_freeswitch_conference.wav"/>
        <action function="play-file" data="ivr/ivr-please.wav"/>
        <action function="play-file" data="voicemail/vm-press.wav"/>
        <action function="play-file" data="digits/1.wav"/>

        <!-- Menu option 2: Do FreeSWITCH echo test -->
        <action function="play-file" data="ivr/ivr-to_do_a_freeswitch_echo_test.wav"/>
        <action function="play-file" data="ivr/ivr-please.wav"/>
        <action function="play-file" data="voicemail/vm-press.wav"/>
        <action function="play-file" data="digits/2.wav"/>

        <!-- Menu option 3: Listen to Music on Hold -->
        <action function="play-file" data="ivr/ivr-to_listen_to_moh.wav"/>
        <action function="play-file" data="ivr/ivr-please.wav"/>
        <action function="play-file" data="voicemail/vm-press.wav"/>
        <action function="play-file" data="digits/3.wav"/>

        <!-- Menu option 4: Register for ClueCon -->
        <action function="play-file" data="ivr/ivr-register_for_cluecon.wav"/>
        <action function="play-file" data="digits/4.wav"/>

        <!-- Menu option 5: Listen to screaming monkeys -->
        <action function="play-file" data="ivr/ivr-to_hear_screaming_monkeys.wav"/>
        <action function="play-file" data="ivr/ivr-please.wav"/>
        <action function="play-file" data="voicemail/vm-press.wav"/>
        <action function="play-file" data="digits/5.wav"/>

        <!-- Menu option 6: Hear a sample submenu -->
        <action function="play-file" data="ivr/ivr-to_hear_sample_submenu.wav"/>
        <action function="play-file" data="ivr/ivr-please.wav"/>
        <action function="play-file" data="voicemail/vm-press.wav"/>
        <action function="play-file" data="digits/6.wav"/>

        <!-- Menu option 9: Repeat these options -->
        <action function="play-file" data="ivr/ivr-to_repeat_these_options.wav"/>
        <action function="play-file" data="ivr/ivr-please.wav"/>
        <action function="play-file" data="voicemail/vm-press.wav"/>
        <action function="play-file" data="digits/9.wav"/>
        <action function="play-file" data="silence_stream://2000"/>
      </match>
    </input>
  </macro>

参数介绍:

1.2.1 macro

短语宏标签

1.2.2 macro-name

短语宏名称,用于拨号方案中调用短语宏时的引用的内容。

1.2.3 macro-pause

(可选)指定每个播放文件播放完后,休眠的时间。单位时毫秒,此处100表示100毫秒。

1.2.4 input-pattern

此处是正则匹配参数。也就是上面提到的宏参数。如果匹配到,执行子节点<match>,反之执行<nomatch>
大多数,不会向短语传递任何参数,灵活应用。

1.2.5 action

这个比较熟悉,路由表中基本都有,这就是执行短语宏要执行的动作的标签,通常是"play- file""sleep" (play-file 表示播放文件,sleep 表示停止多长时间执行下一个play-file, 另一个有用的action叫"say",它以特定的语言(英语、汉语等)规则,正确播报数字、日期、数量、货币、序号、拼写等信息。)参数data是音频路径,这儿可以是绝对路径,也可以是相对路径。

相对路径是相对于 ${sound_prefix}的路径,定义在/user/local/freeswitch/conf/vars.xml中,可以使用命令:eval $${sound_prefix} 查询。

freeswitch注册分机 freeswitch常用命令_git_05


其实,

<action functinotallow="play-file" data="ivr/ivr-welcome_to_freeswitch.wav"/>

<action function="play-file" data="$${sound_prefix}ivr/ivr-welcome_to_freeswitch.wav"/>

<action function="play-file" data="/usr/local/freeswitch/sounds/en/us/callie/ivr/ivr-welcome_to_freeswitch.wav"/>

表达的意思是一致的。

注:
如果你的呼叫使用PCMU编码(比如说G711),因为这种编码的采样率是8khz,所以,相对路径"ivr/ivr-welcome_to_freeswitch.wav"将会被解析为” /usr/local/freeswitch/sounds/en/us/callie/ivr/8000/ivr-welcome_to_freeswitch.wav”,IVR将读取并播放这个文件。如果这个文件不存在,FreeSWITCH将首先使用48khz采样率的同名文件(/usr/local/freeswitch/sounds/en/us/callie/ivr/48000/ivr- welcome_to_freeswitch.wav),因为FreeSWITCH音频混音的格式是48khz的。如果还找不到,再尝试32khz,再然后是16khz。如果还是没找到,FreeSWITCH会查找/usr/local/freeswitch/sounds/en/us/callie/ivr/ivr- welcome_to_freeswitch.wav文件(在路径中不插入采样率),并通过自动检测采样率播放文件。如果所有地方都找不到,FreeSWITCH将输出一条错误日志并继续播放下一个音频文件。

1.3 menu-greet-short

定义返回主菜单的短语宏。

1.4 menu-invalid-sound

当呼叫者输入的DTMF与菜单“条目”不符时读取的内容。
注:DTMF 暂时理解为 按键值。比如:按键1,DTMF值为1

1.5 menu-exit-sound

退出菜单,用户挂机前播报的内容(因为输入错误超限,超时等原因退出菜单)

1.6 menu-confirm-macro

1.7 menu-confirm-key

用户结束序列输入时的按键。默认值是#。

1.8 menu-tts-engine

FreeSWITCH配置文件中注册的TTS引擎名称,通常测试用"flite",生产环境用"cepstral"。

1.9 menu-tts-voice

这是传递给TTS引擎的"voice"参数值。TTS引擎能够提供不同的声音转换,比如说男声,女声或方言。令人好奇的是,flite TTS引擎提供的"rms"声音是从GNU项目创始人Richard Marshall Stallman的声音采样出来的。

1.10 menu-confirm-attempts

1.11 menu-timeout

greet-longgreet-short播报之后,等待用为输入DTMF的时间,单位是毫秒。如果超时触发,会重复播报欢迎辞greet-short,最多尝试max-timeout指定的次数。

1.12 menu-max-failures

菜单转到exit-sound并挂机之前,超时重试的次数限定,每次超时,如果没有超限,将播报invalid-sound,并重新播报欢迎辞。

1.13 menu-max-timeouts

参考 menu-timeout

1.14 menu-digit-len

单词允许输入的最大位数。
例如,你可能希望把它设置为4,这样对应演示配置实例中,"Local_Extension"对应的分机号长度,这样可以接受1001到1019这些号码的输入。允许用户通过“确认键”(通常是#)或者等待超时(“inter-digit-timeout”)输入较短的号码序列(不满四位的号码)。

1.15 entry-action

1.15.1 menu-exec-app

这是IVR菜单的主力。它允许执行一个FreeSWITCH拨号方案的APP,还可以向APP传递参数。

1.15.1 menu-play-sound

播放一个声音文件(以绝对路径或相对路径定位);或者播放一个"phrase:"结构;或者一个TTS的"say:"结构。

1.15.1 menu-sub

执行(跳转到)另一个XML语音菜单,通过菜单名标识。

1.15.1 menu-back

从一个子菜单跳转回上一层XML语音菜单。

1.15.1 menu-top

从子菜单直接跳转回顶层入口的主菜单。

1.15.1 menu-exit

你可能不相信,但事实在它就是存在,望文生义,它退出菜单!

1.16 entry-digits

用户发过来触发菜单执行action的完整DTMF序列

1.16 entry-param

传递给action的参数描述

freeswitch注册分机 freeswitch常用命令_路由表_06


这些参数一目了然,bridge 桥接B-leg,transfer 转到 default context 的路由表中,并执行路由表。

2、测试

通过上面的流程,其实很简单,但是我们了解了IVR的运行机制,从最初的路由表,到最终核心逻辑执行menu--exec-appaction。也能够随意copy demo,去实现自己的需求。

我们可以用测试电话 eyeBeam,拨打5000就进入了IVR逻辑,会听到一段欢迎词,并且提示输入按键,我们输入2,即执行回音路由流程,电话端就听到了自己的回音。

3、扩展

3.1 嵌套菜单

方案一:通过menu-sub 这个 action,跳转到 其他菜单

freeswitch注册分机 freeswitch常用命令_路由表_07


方案二:通过menu-exec-app执行参数transfer 的方式,通过路由表,再转到 其他菜单