网站首页 > 技术文章 正文
Asterisk 通道和ARI详解
什么是通道
Asterisk中,通道是介于终端和Asterisk自己本身的一个通信媒介。它包含了所有相关信息传递到终端,或者从终端传递到Asterisk服务器端。这些信息中包含了信令(设备状态或挂机命令)和媒体(从终端发送或者接收的语音和视频)。
当通道创建以后,这表示成功创建了一个通信的介质,Asterisk会指定两个变量,一个是UniqueID -它用来处理通道的整个生命周期。另外一个是unique Name。UniqueID是一个全局的唯一标识符,可以由ARIclient。如果 ARI client不能给通道提供UniqueID,那么Asterisk会给通道设置一个UniqueID。默认环境下,它会使用Unix时间戳带一个不断递增的整数 ,和可选的Asterisk系统名称。
通道和终端的绑定关系
通道名称有两部分组成:已创建的通道类型带一个可描述的通道类型标志符。支持的通道类型取决于Asterisk的配置。这里,我们使用PJSIP通道来和SIP终端设备进行通信。
在上面的例子中,Alice的SIP终端设备呼叫Asterisk,Asterisk会指定一个通道带一个UniqueID-Asterisk01-123456789.1,PJSIP通道驱动器也指定了一个名称PJSIP名称- PJSIP/Alice-00000001。为了对此通道进行控制,ARI操作会使用UniqueIDAsterisk01-123456789.1来进行操作修改的处理。
内部通道- Local Channels
大部分的通道是介于外部终端和Asterisk本身进行通信。Asterisk也可以创建内部的通道。这些通道称之为 Local 通道-它们用来帮助处理Asterisk中各种资源之间的媒体转移。
Local channel 是一种非常特别的通道,它们总是以一对通道的方式出现。如果系统创建一个Local"通道"的话,实际上会创建出两个通道。在local通道之间会出现一个virtualendpoint,它负责来发送local通道之间的媒体。Local通道中的其中一端是和virtualendpoint永久绑定的,但是另外一端的Local通道则可以通过任何方式被修改或者被控制。Local通道的双方会各自对对端发送媒体。
在以上的例子中,ARI已创建了一个Localchannel, Local/myapp@default.同样,Asterisk会创建一对Local通道,带了UniqueIDsof Asterisk01-123456790.1 和 Asterisk01-123456790.2。Local通道的名称分别是Local/myapp@default-00000000;1 和Local/myapp@default-00000000;2 -这里的 ;1 和;2 表示Local通道的两个部分。
在Stasis应用程序中的通道
当通道在Asterisk中创建以后,它会开始执行Asterisk拨号规则。Asterisk通过context/extension/priority 的方式定义了进入到拨号规则的通道。通道在不同的层级会执行不同的Asterisk应用程序。当优先级增加以后,拨号规则会自动进入到下一个的优先级和相应的应用程序,拨号规则会继续执行,直到最后拨号规则的应用程序通知通道挂机或终端设备自己挂机。
ARI控制通道是通过Stasis拨号规则的应用程序。这个特别的程序会从拨号规则中来控制通道,ARI客户端连接一个websocket,这个websocket已经控制了通道。这里已经启动了一个StasisStart事件;当通道离开这个Stasis拨号规则的应用程序后-或者它被告知离开,或者因为设备挂机,然后启动一个StasisEnd事件。当这个StasisEnd事件启动以后,ARI不在控制这个通道,通道从ARI释放出来,返回到拨号规则中。
在Asterisk的资源默认情况下不会自己发送事件连接ARI应用程序。为了获得资源的事件,必须满足其中以下之一的条件:
资源必须是已进入Stasis拨号规则应用的一个通道。在这个环境中,订阅是在后台创建。当通道离开Stasis拨号规则的应用程序后,订阅也是在后台被销毁。
当通道进入到Stasis拨号规则的程序后,通道可能会和其他资源进行互动-例如bridge。当通道和这些资源互动时,订阅事件已经被这个资源启动。当在Stasis拨号规则中无任何通道后,后台的订阅事件被销毁。
在任何时候,一个ARI程序可以在Asterisk中通过应用程序来订阅资源。资源操作的话,ARI应用程序就会有自己的订阅事件。
举例:和通道进行通信
在这个例子中,我们会按照以下方式写一个ARI应用程序:
当ARI连接以后,它会打印出当前通道的名称。如果无通道存在,也会有提示信息。
当通道进入到Stasis程序后,它会打印出关于通道的信息。
当通道离开Stasis程序后,它也会打印离开的通道。
拨号规则
此拨号规则非常直接:一个extension的通道进入Stasis程序。
extensions.conf
123456 | [default]exten => 1000,1,NoOp() same => n,Answer() same => n,Stasis(channel-dump) same => n,Hangup() |
Python实例
在Python例子中,我们是基于ari-py这个支持包来实现的。因为ari支持包通过Python日志系统支持了丰富的信息,我们可以直接设置使用。带Error的 basicConfig 可以显示基本的错误信息,它完全可以支持基本的工作需求。最后,我们需要一个终端来发起对Asterisk的连接。终端使用的方式是ari.connect 的方式,这里,我们需要设置三个参数:
一个HTTP URL连接。这里,我们假设脚本运行在同一台服务器,使用Asterisk HTTP 服务器默认端口- 8088。
使用的是ARI用户名称连接服务器。这里,我们设置为asterisk。
ARI账号密码是asterisk。
注意:
当用户是生产系统时,请修改默认的用户名和密码。
12345678 | #!/usr/bin/env pythonimport ariimport logginglogging.basicConfig(level=logging.ERROR)client = ari.connect('http://localhost:8088', 'asterisk', 'asterisk') |
一旦我们发起了连接,第一个任务是打印出当前所有的通道,如果没有通道则打印无通道信息。通道资源可以支持- GET/channels操作。因为ari-py 支持包是动态操作的,它操作的对象结构可以通过映射到方式来进行,我们这里可以使用列出所有通道资源的列表来实现对通道的各种控制:
10111213141516 | current_channels = client.channels.list()if (len(current_channels) == 0): print "No channels currently :-("else: print "Current channels:" for channel in current_channels: print channel.json.get('name') |
通过GET/channels 的操作可以获得通道资源的列表。这些资源是从操作中通过JSON的格式返回,同时,ari-py支持包会转换uniqueid 的用户属性来附加到操作对象,它会把其他的信息保存在JSON目录下。因为,这里我们仅需要此名称,所以,我们这里仅通过JSON提取名称,然后打印此名称。
下一步,当通道进入到Stasis应用程序channel-dump后,我们打印出通道的相关信息,离开时打印出通道名称。为了实现以上要求,我们需要订阅StasisStart 和 StasisEnd 事件:
3233 | client.on_channel_event('StasisStart', stasis_start_cb)client.on_channel_event('StasisEnd', stasis_end_cb) |
这里,我们仍然需要两个函数来处理stasis_start_cb 负责StasisStart 的事件,stasis_end_cb 处理StasisEnd 事件:
18192021222324252627282930 | def stasis_start_cb(channel_obj, ev): """Handler for StasisStart event""" channel = channel_obj.get('channel') print "Channel %s has entered the application" % channel.json.get('name') for key, value in channel.json.items(): print "%s: %s" % (key, value)def stasis_end_cb(channel, ev): """Handler for StasisEnd event""" print "%s has left the application" % channel.json.get('name') |
最后,系统需要告诉终端来运行应用程序。一旦client.run执行以后,系统会做一个websocket连接,应用程序会一直等待触发的事件。用户可以使用 Ctrl+C 组合键来杀死这个连接。
35 | client.run(apps='channel-dump') |
channel-dump.py
完整的channel-dump.py 代码:
channel-dump.py
1234567891011121314151617181920212223242526272829303132333435 | #!/usr/bin/env pythonimport ariimport logginglogging.basicConfig(level=logging.ERROR)client = ari.connect('http://localhost:8088', 'asterisk', 'asterisk')current_channels = client.channels.list()if (len(current_channels) == 0): print "No channels currently :-("else: print "Current channels:" for channel in current_channels: print channel.json.get('name')def stasis_start_cb(channel_obj, ev): """Handler for StasisStart event""" channel = channel_obj.get('channel') print "Channel %s has entered the application" % channel.json.get('name') for key, value in channel.json.items(): print "%s: %s" % (key, value)def stasis_end_cb(channel, ev): """Handler for StasisEnd event""" print "%s has left the application" % channel.json.get('name')client.on_channel_event('StasisStart', stasis_start_cb)client.on_channel_event('StasisEnd', stasis_end_cb)client.run(apps='channel-dump') |
channel-dump.py 实战测试
以下是channel-dump.py的输出结果。第一次连接以后,没有发现任何的通道,然后Alice(extension1000)使用的PJSIP通道进入到Stasis应用程序.输出打印所有Alice的通道信息。一段时间以后,她挂机离开了应用程序。
asterisk:~$ python channel-dump.py
No channels currently :-(
Channel PJSIP/alice-00000001 has entered the application
accountcode:
name: PJSIP/alice-00000001
caller: {u'Alice': u'', u'6575309': u''}
creationtime: 2014-06-09T17:36:31.698-0500
state: Up
connected: {u'name': u'', u'number': u''}
dialplan: {u'priority': 3, u'exten': u'1000', u'context': u'default'}
id: asterisk-01-1402353503.1
PJSIP/alice-00000001 has left the application
JavaScript (Node.js)
在这个例子中,我们是基于Node.js ari-client 这个支持包来实现的。我们需要一个终端来发起对Asterisk的连接。终端使用的方式是ari.connect 的方式,这里,我们需要设置三个参数:
一个HTTP URL连接。这里,我们假设脚本运行在同一台服务器,使用Asterisk HTTP 服务器默认端口- 8088。
使用的是ARI用户名称连接服务器。这里,我们设置为asterisk。
ARI账号密码是asterisk。
注意:
在生成系统中,一定要修改实例中的用户名称和密码。
1234567891011121314 | /*jshint node:true*/'use strict';var ari = require('ari-client');var util = require('util');ari.connect('http://localhost:8088', 'asterisk', 'asterisk', clientLoaded);// handler for client being loadedfunction clientLoaded (err, client) { if (err) { throw err; }} |
一旦我们发起了连接,第一个任务是打印出当前所有的通道,如果没有通道则打印无通道信息。通道资源可以支持- GET/channels操作。因为ari-client支持包是动态操作的,它操作的对象结构可以通过映射到方式来进行,我们这里可以使用列出所有通道资源的列表来实现对通道的各种控制:
15161718192021222324 | client.channels.list(function(err, channels) { if (!channels.length) { console.log('No channels currently :-('); } else { console.log('Current channels:'); channels.forEach(function(channel) { console.log(channel.name); }); }}); |
通过GET/channels 的操作可以获得通道资源的列表。这些资源是从操作中通过JSON的格式返回,同时,ari-client支持包会转换uniqueid 的用户属性来附加到操作对象,它会把其他的信息保存在JSON目录下。因为,这里我们仅需要此名称,所以,我们这里仅通过JSON提取名称,然后打印此名称。
下一步,当通道进入到Stasis应用程序channel-dump后,我们打印出通道的相关信息,离开时打印出通道名称。为了实现以上要求,我们需要订阅StasisStart 和 StasisEnd 事件:
4344 | client.on('StasisStart', stasisStart);client.on('StasisEnd', stasisEnd); |
这里,我们仍然需要两个函数来处理stasis_start_cb 负责StasisStart的事件,stasis_end_cb 处理StasisEnd 事件:
2627282930313233343536373839 | //handler for StasisStart eventfunction stasisStart(event, channel) { console.log(util.format( 'Channel %s has entered the application', channel.name)); // use keys on event since channel will also contain channel operations Object.keys(event.channel).forEach(function(key) { console.log(util.format('%s: %s', key, JSON.stringify(channel[key]))); });}// handler for StasisEnd eventfunction stasisEnd(event, channel) { console.log(util.format( 'Channel %s has left the application', channel.name));} |
最后,系统需要通知终端来运行应用程序。一旦client.run执行以后,系统会做一个websocket连接,应用程序会一直等待触发的事件。用户可以使用 Ctrl+C 组合键来杀死这个连接。
46 | client.start('channel-dump'); |
channel-dump.js
完整的channel-dump.js 源代码:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647 | /*jshint node:true*/'use strict';var ari = require('ari-client');var util = require('util');ari.connect('http://localhost:8088', 'asterisk', 'asterisk', clientLoaded);// handler for client being loadedfunction clientLoaded (err, client) { if (err) { throw err; } client.channels.list(function(err, channels) { if (!channels.length) { console.log('No channels currently :-('); } else { console.log('Current channels:'); channels.forEach(function(channel) { console.log(channel.name); }); } }); // handler for StasisStart event function stasisStart(event, channel) { console.log(util.format( 'Channel %s has entered the application', channel.name)); // use keys on event since channel will also contain channel operations Object.keys(event.channel).forEach(function(key) { console.log(util.format('%s: %s', key, JSON.stringify(channel[key]))); }); } // handler for StasisEnd event function stasisEnd(event, channel) { console.log(util.format( 'Channel %s has left the application', channel.name)); } client.on('StasisStart', stasisStart); client.on('StasisEnd', stasisEnd); client.start('channel-dump');} |
channel-dump.js 实战测试
以下是channel-dump.js的输出结果。第一次连接以后,没有发现任何的通道,然后Alice(extension1000)使用的PJSIP通道进入到Stasis应用程序.输出打印所有Alice的通道信息。一段时间以后,她挂机离开了应用程序。
123456789101112 | asterisk:~$ node channel-dump.jsNo channels currently :-(Channel PJSIP/alice-00000001 has entered the applicationaccountcode:name: PJSIP/alice-00000001caller: {u'Alice': u'', u'6575309': u''}creationtime: 2014-06-09T17:36:31.698-0500state: Upconnected: {u'name': u'', u'number': u''}dialplan: {u'priority': 3, u'exten': u'1000', u'context': u'default'}id: asterisk-01-1402353503.1PJSIP/alice-00000001 has left the application |
猜你喜欢
- 2024-10-30 软交换折腾小记 软交换与ngn
- 2024-10-30 Asterisk-ARI对通道中的DTMF事件处理
- 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)
本文暂时没有评论,来添加一个吧(●'◡'●)