网站首页 > 技术文章 正文
Asterisk通道中关于DTMF处理是一个非常重要的功能。通过DTMF可以实现很多的业务处理。现在我们介绍一下关于ARI对通道中的DTMF处理,我们通过自动话务员实例来说明Asterisk如何创建一个拨号规则,如何在Stasis中执行脚本语言,在脚本中对DTMF实现整个菜单的处理。
本文的实例是结合了Python和JavaScript两种开发语言。因为担心用户微信阅读中的阅读体验不好,所以,我们仅列出了Python代码,JavaScript的完整代码可以通过论坛获得。
DTMF事件处理
DTMF事件是通过ChannelDtmfReceived来传输。这个事件中包含了通道中的相关信息,它们可能是用户输入的DTMF键,数字和数字长度。
作为基本电话系统的要求,系统必须支持处理DTMF,然后根据DTMF来执行某些操作。这些操作包括控制媒体,发起呼叫功能,执行电话转接,拨号等等。以下实例仅说明如何处理DTMF事件,而不是针对DTMF相关功能的处理。
实例:自动话务员
以下是一个典型的自动话务员/IVR的拨号规则。它需要执行以下几步:
对系统用户播放一个语音菜单,当用户执行某一个命令时,取消语音菜单提示。
如果用户按1或者2时,重新进入菜单。
如果用户输入无效的数字时,系统对用户播放一个提示语音指示此数字是无效的,然后重新进入菜单。
如果用户在一定时间内没有输入任何数字,要求用户输入数字,然后重新进入到菜单播放流程。
注意,执行以下实例,系统安装需要满足几个条件:
安装extra 语音包,用户可以通过
menuselect
编译语音包安装。
如果使用Python 的例子,
ari-py
版本必须是0.1.3或更高版本。
如果使用JavaScript的例子,ari-client版本必须是0.1.4或者更高版本。
创建一个拨号规则
这是一个简单的拨号规则,可以满足我们实例的要求。拨号规则把通道引入到Stasis应用程序
channel-aa中。
exten =>1000,1,NoOp()
same =>n,Stasis(channel-aa) // 进入到用户指定的用户程序
same =>n,Hangup
(拨号规则)
Python开发实例
因为这个实例代码比较多,所以把代码分成两个部分来介绍:
构建一个语音菜单,当用户按按钮时处理相应的状态。
用户按键输入以后的处理流程。
播放语音菜单处理
不像Playback语音回放功能,它可以连续播放几个语音文件,然后重新回放这些语音文件,ARI则会把这些文件看作是一个文件播放。这些文件会按照队列的形式在通道中播放,然后返回到呼叫方一个对象,以便呼叫方对其单一文件进行操作控制。此菜单对话务员有以下要求:
对用户回放选项
如果用户按了一个DTMF键以后,取消回放选项,处理用户的输入请求
如果用户按了无效的DTMF按键,系统通知用户,重新开始语音菜单
如果用户在10秒内无任何输入,要求用户重新输入,重新启动菜单
对于第二个要求的处理可能所谓复杂一点。当用户按DTMF键时,我们需要取消掉正在播放的语音文件,马上处理下一步的请求。我们必须维持当前播放的语音文件状态,以便需要播放时能够重新回放。保证前面的语音文件完成后可以进行下一个语音文件的播放。
取消菜单处理
当用户按DTMF键后,系统可以停止当前的回放,结束菜单。为了实现这个功能,我们需要从通道订阅 DTMF事件。这里,我们需要定义一个新的函数
cancel_menu,当DTMF按键通过
ChannelDtmfReceived事件收到以后,
它通知
ari-py
调用这个函数。这里,我们不真正关心具体的数字按键,我们仅执行取消菜单的流程。在这个函数中,系统会设置
menu_state.complete
为
True,然后通知
current_playback停止播放。
超时处理
现在系统可以取消菜单,当然,我们也可以重新启动菜单流程,当系统用户没有收入任何按键时,超时后重新启动菜单。这里,我们使用了一个Python 定时器来控制这个时间。如果用户输入了按键以后,我们则不会启动这个定时器。在一定时间范围内,用户没有收入数字按键,定时器会调用menu_timeout,这个函数会播放一个 "are you still there?" 的提示音,然后重新启动菜单。
对DTMF的处理选项
现在系统可以对用户回放菜单,实际上,我们还要实现一个自动话务员的菜单。系统可以在
StasisStart
中注册
ChannelDtmfReceived
事件,所以,我们需要做以下几个事情:
可以取消和任何通道关联的定时器。
如果数字是1或2时处理输入的数字。
如果输入的数字不支持的话,对系统用户播放一个提示音,提示输入数字无效,然后重新播放菜单选项。
完整Python代码:channel-aa.py
#!/usr/bin/env python
import
ari
import
logging
import
threading
logging.basicConfig(level
=
logging.ERROR)
client
=
ari.connect(
'http://localhost:8088'
,
'asterisk'
,
'asterisk'
)
# Note: this uses the 'extra' sounds package
sounds
=
[
'press-1'
,
'or'
,
'press-2'
]
channel_timers
=
{}
class
MenuState(
object
):
"""A small tracking object for the channel in the menu"""
def
__init__(
self
, current_sound, complete):
self
.current_sound
=
current_sound
self
.complete
=
complete
def
play_intro_menu(channel):
"""Play our intro menu to the specified channel
Since we want to interrupt the playback of the menu when the user presses
a DTMF key, we maintain the state of the menu via the MenuState object.
A menu completes in one of two ways:
(1) The user hits a key
(2) The menu finishes to completion
In the case of (2), a timer is started for the channel. If the timer pops,
a prompt is played back and the menu restarted.
Keyword Arguments:
channel The channel in the IVR
"""
menu_state
=
MenuState(
0
,
False
)
def
play_next_sound(menu_state):
"""Play the next sound, if we should
Keyword Arguments:
menu_state The current state of the IVR
Returns:
None if no playback should occur
A playback object if a playback was started
"""
if
(menu_state.current_sound
=
=
len
(sounds)
or
menu_state.complete):
return
None
try
:
current_playback
=
channel.play(media
=
'sound:%s'
%
sounds[menu_state.current_sound])
except
:
current_playback
=
None
return
current_playback
def
on_playback_finished(playback, ev, menu_state):
"""Callback handler for when a playback is finished
Keyword Arguments:
playback The playback object that finished
ev The PlaybackFinished event
menu_state The current state of the menu
"""
queue_up_sound(channel, menu_state)
def
queue_up_sound(channel, menu_state):
"""Start up the next sound and handle whatever happens
Keywords Arguments:
channel The channel in the IVR
menu_state The current state of the menu
"""
def
menu_timeout(channel):
"""Callback called by a timer when the menu times out"""
'Channel %s stopped paying attention...'
%
channel.json.get(
'name'
)
channel.play(media
=
'sound:are-you-still-there'
)
play_intro_menu(channel)
def
cancel_menu(channel, ev, current_playback, menu_state):
"""Cancel the menu, as the user did something"""
menu_state.complete
=
True
try
:
current_playback.stop()
except
:
pass
return
current_playback
=
play_next_sound(menu_state)
if
not
current_playback:
if
menu_state.current_sound
=
=
len
(sounds):
# Menu played, start a timer!
timer
=
threading.Timer(
10
, menu_timeout, [channel])
channel_timers[channel.
id
]
=
timer
timer.start()
return
menu_state.current_sound
+
=
1
current_playback.on_event(
'PlaybackFinished'
, on_playback_finished,
callback_args
=
[menu_state])
# If the user hits a key or hangs up, cancel the menu operations
channel.on_event(
'ChannelDtmfReceived'
, cancel_menu,
callback_args
=
[current_playback, menu_state])
channel.on_event(
'StasisEnd'
, cancel_menu,
callback_args
=
[current_playback, menu_state])
queue_up_sound(channel, menu_state)
def
handle_extension_one(channel):
"""Handler for a channel pressing '1'
Keyword Arguments:
channel The channel in the IVR
"""
channel.play(media
=
'sound:you-entered'
)
channel.play(media
=
'digits:1'
)
play_intro_menu(channel)
def
handle_extension_two(channel):
"""Handler for a channel pressing '2'
Keyword Arguments:
channel The channel in the IVR
"""
channel.play(media
=
'sound:you-entered'
)
channel.play(media
=
'digits:2'
)
play_intro_menu(channel)
def
cancel_timeout(channel):
"""Cancel the timeout timer for the channel
Keyword Arguments:
channel The channel in the IVR
"""
timer
=
channel_timers.get(channel.
id
)
if
timer:
timer.cancel()
del
channel_timers[channel.
id
]
def
on_dtmf_received(channel, ev):
"""Our main DTMF handler for a channel in the IVR
Keyword Arguments:
channel The channel in the IVR
digit The DTMF digit that was pressed
"""
# Since they pressed something, cancel the timeout timer
cancel_timeout(channel)
digit
=
int
(ev.get(
'digit'
))
'Channel %s entered %d'
%
(channel.json.get(
'name'
), digit)
if
digit
=
=
1
:
handle_extension_one(channel)
elif
digit
=
=
2
:
handle_extension_two(channel)
else
:
'Channel %s entered an invalid option!'
%
channel.json.get(
'name'
)
channel.play(media
=
'sound:option-is-invalid'
)
play_intro_menu(channel)
def
stasis_start_cb(channel_obj, ev):
"""Handler for StasisStart event"""
channel
=
channel_obj.get(
'channel'
)
"Channel %s has entered the application"
%
channel.json.get(
'name'
)
channel.on_event(
'ChannelDtmfReceived'
, on_dtmf_received)
play_intro_menu(channel)
def
stasis_end_cb(channel, ev):
"""Handler for StasisEnd event"""
"%s has left the application"
%
channel.json.get(
'name'
)
cancel_timeout(channel)
client.on_channel_event(
'StasisStart'
, stasis_start_cb)
client.on_channel_event(
'StasisEnd'
, stasis_end_cb)
client.run(apps
=
'channel-aa'
)
channel-aa.py 实战输出结果
以下是执行
channel-aa.py
输出的结果,当PJSIP通道收到按键输入1,2,8后,超时的流程,最后挂机。
Channel PJSIP/alice-
00000001
has entered the application
Channel PJSIP/alice-
00000001
entered
1
Channel PJSIP/alice-
00000001
entered
2
Channel PJSIP/alice-
00000001
entered
8
Channel PJSIP/alice-
00000001
entered an invalid option!
Channel PJSIP/alice-
00000001
stopped paying attention...
PJSIP/alice-
00000001
has left the application
以上例子是一个自动话务员的实例。在ARI对通道的DTMF处理,基本上都需要经过以上几个个方面的处理,它们包括:创建拨号规则,通过拨号规则进入到Stasis应用程序中,脚本语言负责执行语音播放处理,DTMF选项处理,无效输入处理,和超时处理。
因为微信中插入大量代码会影响用户的阅读体验,完整的Python和Javasript的源代码可以访问论坛查看。
- 上一篇: Asterisk通道和ARI接口的通信 alphy通道
- 下一篇: 软交换折腾小记 软交换与ngn
猜你喜欢
- 2024-10-30 软交换折腾小记 软交换与ngn
- 2024-10-30 Asterisk通道和ARI接口的通信 alphy通道
- 2024-10-30 使用WebRTC技术实现浏览器和其他电话终端之间的呼叫
- 2024-10-30 网易云信CTO阙杭宁:如何保障十万不同应用的通讯稳定与实时性
- 2024-10-30 融合通信功能配置示例-1-如何快速批量添加SIP分机
- 2024-10-30 PJSIP集成G729 (vs2022版) 乚a7841集成块
- 2024-10-30 JFrog 披露PJSIP开源多媒体通信库的五个漏洞
- 2024-10-30 chan_pjsip三个必要设置解决SIP呼叫问题
- 2024-10-30 PJSIP 2.12.1 编译 与 C# Pjsip (Pjsua2 api )应用
你 发表评论:
欢迎- 最近发表
-
- 在 Spring Boot 项目中使用 activiti
- 开箱即用-activiti流程引擎(active 流程引擎)
- 在springBoot项目中整合使用activiti
- activiti中的网关是干什么的?(activiti包含网关)
- SpringBoot集成工作流Activiti(完整源码和配套文档)
- Activiti工作流介绍及使用(activiti工作流会签)
- SpringBoot集成工作流Activiti(实际项目演示)
- activiti工作流引擎(activiti工作流引擎怎么用)
- 工作流Activiti初体验及在数据库中生成的表
- Activiti工作流浅析(activiti6.0工作流引擎深度解析)
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)