Android 语音通话模块介绍(一)

PJSIP简介

PJSIP 是一个开放源代码的 SIP 协议栈;官网地址( http://www.pjsip.org/ ),它支持多种 SIP 的扩展功能 。 PJLIB, PJLIB-UTIL, PJMEDIA, and PJSIP are released under dual open source GPL or alternative license.

PJSIP包括的内容

PJSIP - Open Source SIP Stack[ 开源的 SIP 协议栈 ] PJMEDIA - Open Source Media Stack[ 开源的媒体栈 ] PJNATH - Open Source NAT Traversal Helper Library[ 开源的 NAT-T 辅助库 ] PJLIB-UTIL - Auxiliary Library[ 辅助工具库 ] PJLIB - Ultra Portable Base Framework Library[ 基础框架库 ]

PJSIP的优点

a、高度的可移殖性
只需简单的编译一次,它能够在多种平台上运行(所有 Windows 系统列 , Windows Mobile, Linux, 所有 Unix 系列 , MacOS X, RTEMS, Symbian OS, 等等)。 b、 极小的内存需求
官方宣称编译后的库,完全实现 SIP 的功能只需要 150K 的内存空间,这使得 PJISPi 不仅仅是嵌入开发的理想平台,并且实用于那些内存运行于极小内存平台的应用,这也意味着极小的用户下载时间。 c、高效的性能
这意味着极小的 CPU 运算需求下能同时实现更多的通话。 d、支持多种 SIP 功能及扩展功能
多种 SIP 功能和扩展功能,例如多人会话,事件驱动框架,会话控制( presence ),即时信息,电话传输,等等在库文件里得以实现。 e、丰富的文档资料

PJSIP开发人员提供了大量的极有价值的文档资料供大家使用。

PJMEDIA简介

PJMEDIA 是一个为 PJSIP 建立一个完整特性 SIP 用户代理应用提供的补充库,这些应用包括: softphones/hardphones gateways or B2BUA. 使用 PJSIP PJMEDIA 一起开发的应用,具备如下的特性: a、 高度的可移殖性
PJSIP/PJLIB 一起, PJMEDIA 可运行在许多平台上,包括服务器、桌面、 PDA 系统,定制的硬件、 PDA 或移动电话。 b、多种功能
会议桥接、多种编解码器、 丢包隐蔽 / PLC ,音频发生器,静音探测器,声学回声消除 / AEC RFC2833 RTP / RTCP 协议栈, speex/iLBC/GSM/G.711 编解码器等 c、高质量
PJMEDIA 支持频率为 16KHz 32Khz 的编码和解码,事实上能支持任何音频采样率,可提供高质量的采样转换。 PJMEDIA 也可以容忍一定量的网络或声音设备的不稳定和一些数据包丢失。 d、很好的支持嵌入式 /DSP
占用内存小,灵活性好。该媒体组件被设计成可替换成相应功能的硬件 e、较好的文档资料

PJMEDIA配备了相当不错的文档

PJNATH简介

PJNATH 是一个新的库,帮助应用程序进行 NAT 穿越。它实现了 NAT 穿越的最新规范: STUN TURN ICE PJNATH 可以作为一个独立库,在您的软件中使用,也可以使用 PJSUA- LIB 库,该库很好的与 PJSIP PJMEDIA PJNATH 整合在一起,使用起来比较简单。

PJNATH的特点

a、STUNbis 实现,
实现符合
RFC5389​​ 标准。既提供需要使用的 STUN 网络接口,又提供基于 STUN 但更高层次的框架,既 TURN ICE b、NAT 类型检测,
根据
RFC3489 STUN ) ,在前端可以执行 NAT 类型检测。该检测方法不能对所有 NAT 类型进行穿越,但该信息可能仍然是有用,以便进行故障排除,已经被 ICE 整合,因此提供了该检测方式。 c、TURN 实现,

TURN是一个中继通信协议,通过使用中继,并结合ICE,提供了高效的最低代价的通信路径。PJNATHTURN的实现,符合draft-ietf-behave-turn-14草案。

d、ICE 实现,

ICE是一个发现两个端点之间的通信路径协议。PJNATHICE的实现符合draft-ietf-mmusic-ice-19.txt草案

e、在未来,将实现更多的协议(如 UPnP IGD SOCKS5 )。

PJLIB-UTIL简介

PJLIB-UTIL 是一个辅助库,为 PJMEDIA PJSIP 提供支持。这个库中的一些功能 / 组件:占用内存小的 XML 解析, STUN 客户端库,异步 / 缓存 DNS 解析,哈希 / 加密功能等 。

PJLIB简介

q 占用内存小,高性能,高可移植性的抽象库和框架,被 PJSIP PJMEDIA 使用。

PJLIB PJLIB-UTIL PJMEDIA PJSIP 唯一依赖的库,因为它提供了完整的抽象,不仅仅是操作系统依赖的属性,还包括 LIBC 的抽象,并提供了一些有用的数据结构。

PJLIB基础框架库提供的功能

Ø 内存的处理、数据的存储
. 数据结构的( hash 表、 link 表、二叉树、等)
.caching pool ;缓冲池和内存池 Ø OS 抽象
. 线程、互斥、临界区、锁对象、事件对象
. 定时器
. pj_str_t 字符串 Ø 操作系统级别的函数抽象
.socket 的抽象 ( tcp/udp )
.
文件的读写 Ø 使用前的初始化,使用后的清理

pjsip的整体框架图(图1.1)

如图1.1展示了PJSIP框架的各模块,可以看出从上到下,Applicationpjsua)模块可调用下层所有的模块,也即是PJSUA处于最高层,其整合了下层模块的全部功能以

这也就是为什么我们基本的操作都在PJSUA这里进行。是因为通过PJSUA,我们就能很方便的深入到其他模块中。接着Application模块往下就是PJSUA_lib层,要让应用层(

PJSUA)能更好的调用,当然得有个封装好的库,这个库就是PJSUA_LIB库,称为高层用户代理库,集合SIPMedia以及NAT穿越,所以也就有了往下的PJMEDIA-CODECPJMEDIA(负责SDP协商媒体编码和媒体传输),PJNATH(解决NAT穿越),PJSUA-UA(提供SIP用户代理库),PJSIP-SIMPLE(实现presence和及时消息)PJSIP(核协议栈,SIP协议),PJLIB-UTIL(提供有用的工具函数)以及PJLIB(每个功能根据其所在的层次以及负责的功能提供丰富的接口)等模块。

从实现上来看,最上层为应用层,该层将在Android SDK的框架内,采用Java语言来实现;第二层为JNI层,SIP协议栈有很多种实现,其中,采用C语言的SIP协议栈在效

、速度、系统占用方面有着超越其他库(如Java协议栈)的优势,因此,该方案将在第三层采用纯C语言实现的PJSIP协议栈。为了让Java应用层能调用协议栈层,在两层之

间需要一个衔接的桥梁,这就是JNI层。最后一层是驱动层,这部分一般是由手机厂商来实现的,此处将不做重点介绍。

SIP协议栈及UA

SIP协议栈直接关系到整个系统的质量与效率,许多开源项目基本上都是采用纯C语言开发的PJSIP库。该库采用C语言开发,且源码开放,在兼容性与效率上有明显优势,不仅体积小(完整的SIP封装也不过150 KB),同时还实现了一个内存池,使得安全系数与运行效率大为提高

PJSIP协议栈

PJSIP协议栈遵循标准的SIP协议,采用分层架构:SIPSDP消息编码解析层、传输管理层、SIP终端、事务层、会话层以及应用层等。由于SIP协议采用文本消息发送请求和响应,所以首先需要将SIP消息按照巴斯克范式(ABNF)编码和解析,这就是SIPSDP消息编码解析层所完成的功能。传输管理层用来管理用户代理与服务器之间的请求和相应;SIP终端是PJSIP中转机制的实现,它主要负责管理各个SIP组建,例如像SIP终端实例注册组件,分发消息到事务层、会话层及应用层,回传处理结果,管理定时器、IO队列等;事务层通过状态机机制管理SIP信令,每一次状态机状态的改变都将触发回调函数;会话层负责会话的发起与响应,一般与应用层结合在一起,用于用户交互,不同的平台有不同的实现,这里主要使用AndriodGUI来实现。


PJSIP是一个高度封装的库,实际上它是通过PJSUA子库来实现应用的。一个完整的PJSUA生命周期,首先需要初始化,通过函数init()来实现。在这个函数中,将创建代理、初始化变量和堆栈,以及创建一个UDP传输并在最后启动代理;第二步将为UA添加用户,如果需要的话,还要向服务器注册用户;当用户添加成功后,此时可以建立一个呼叫连接,发起会话;当会话连接成功后,就可以使用SRTP协议实时传输加密后的数据,进行通话。最后的过程是挂起或销毁呼叫。

UA原理

UAUser Agency)是协议栈的具体实现,PJSIP通过封装了的PJSUA来实现,在这一点上,大部分的SIP库都大同小异,在此将介绍UA的工作原理。

一个典型的UA包含UACUser Agency Client)和UASUser Agency Server)两部分。会话由UAC发起。当呼叫发起时,UAC将首先发送“IN-VITE”消息给SIP代理服务器,服务器收到“INVITE”消息后将返回一个应答“200 OK”,并回答“ACK”进行确认,同时通知主叫用户(即会话发起用户)上线通话。如果主叫端(用户端)主动结束会话,UAC将返回“BYE”消息,同时通知服务器;如果用户端收到服务器传来的“BY-E”消息,回答“200”,并结束会话。

服务器端,
UAS收到UAC(用户端)发来的“INVITE”消息,首先从消息中提取出主、被叫对象,然后检查当前是否有空闲信道,若没有则返回“486 BUSY HERE”(即系统忙)消息;接着将检查被叫用户是否在服务区,如果被叫对象不在服务范围,则返回“404 NOT FOUND”(即用户不在服务区);若被叫用户成功上线,则返回“200 OK”,同时准备开始会话。

SIP协议栈一般使用SIP统一资源定位符(URL)来标识,它根据URL来寻址,如集群用户“200”,“300”分别对应SIP用户为“200@192.168. 1.100”,“300@1921681100”。本文中也使用这种方式来测试通信。

Pjsip实例分析

代码:simple_pjsua.c

/** * simple_pjsua.c * * This is a very simple but fully featured SIP user agent, with the  * following capabilities: *  - SIP registration *  - Making and receiving call *  - Audio/media to sound device. * * Usage: *  - To make outgoing call, start simple_pjsua with the URL of remote *    destination to contact. *    E.g.: * simpleua sip:user@remote * *  - Incoming calls will automatically be answered with 200. * * This program will quit once it has completed a single call. */#include #define THIS_FILE"APP"#define SIP_DOMAIN"example.com"#define SIP_USER"alice"#define SIP_PASSWD"secret"/* Callback called by the library upon receiving incoming call */static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,     pjsip_rx_data *rdata){    pjsua_call_info ci;    PJ_UNUSED_ARG(acc_id);    PJ_UNUSED_ARG(rdata);    pjsua_call_get_info(call_id, &ci);    PJ_LOG(3,(THIS_FILE, "Incoming call from %.*s!!", (int)ci.remote_info.slen, ci.remote_info.ptr));    /* Automatically answer incoming calls with 200/OK */    pjsua_call_answer(call_id, 200, NULL, NULL);}/* Callback called by the library when call's state has changed */static void on_call_state(pjsua_call_id call_id, pjsip_event *e){    pjsua_call_info ci;    PJ_UNUSED_ARG(e);    pjsua_call_get_info(call_id, &ci);    PJ_LOG(3,(THIS_FILE, "Call %d state=%.*s", call_id, (int)ci.state_text.slen, ci.state_text.ptr));}/* Callback called by the library when call's media state has changed */static void on_call_media_state(pjsua_call_id call_id){    pjsua_call_info ci;    pjsua_call_get_info(call_id, &ci);    if (ci.media_status == PJSUA_CALL_MEDIA_ACTIVE) {// When media is active, connect call to sound device.pjsua_conf_connect(ci.conf_slot, 0);pjsua_conf_connect(0, ci.conf_slot);    }}/* Display error and exit application */static void error_exit(const char *title, pj_status_t status){    pjsua_perror(THIS_FILE, title, status);    pjsua_destroy();    exit(1);}/* * main() * * argv[1] may contain URL to call. */int main(int argc, char *argv[]){    pjsua_acc_id acc_id;    pj_status_t status;//  创建PJSIP    /* Create pjsua first! */    status = pjsua_create();    if (status != PJ_SUCCESS) error_exit("Error in pjsua_create()", status);//  校验被叫SIP地址是否正确    /* If argument is specified, it's got to be a valid SIP URL */    if (argc > 1) {status = pjsua_verify_url(argv[1]);if (status != PJ_SUCCESS) error_exit("Invalid URL in argv", status);    }//    初始化PJSUA,设置回调函数    /* Init pjsua */    {pjsua_config cfg;pjsua_logging_config log_cfg;pjsua_config_default(&cfg);cfg.cb.on_incoming_call = &on_incoming_call;cfg.cb.on_call_media_state = &on_call_media_state;cfg.cb.on_call_state = &on_call_state;pjsua_logging_config_default(&log_cfg);log_cfg.console_level = 4;status = pjsua_init(&cfg, &log_cfg, NULL);if (status != PJ_SUCCESS) error_exit("Error in pjsua_init()", status);    }//    创建PJSIP的传输端口    /* Add UDP transport. */    {pjsua_transport_config cfg;pjsua_transport_config_default(&cfg);cfg.port = 5060;status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &cfg, NULL);if (status != PJ_SUCCESS) error_exit("Error creating transport", status);    }//    启动PJSIP    /* Initialization is done, now start pjsua */    status = pjsua_start();    if (status != PJ_SUCCESS) error_exit("Error starting pjsua", status);//    设置SIP用户帐号    /* Register to SIP server by creating SIP account. */    {pjsua_acc_config cfg;pjsua_acc_config_default(&cfg);cfg.id = pj_str("sip:" SIP_USER "@" SIP_DOMAIN);cfg.reg_uri = pj_str("sip:" SIP_DOMAIN);cfg.cred_count = 1;cfg.cred_info[0].realm = pj_str(SIP_DOMAIN);cfg.cred_info[0].scheme = pj_str("digest");cfg.cred_info[0].username = pj_str(SIP_USER);cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;cfg.cred_info[0].data = pj_str(SIP_PASSWD);status = pjsua_acc_add(&cfg, PJ_TRUE, &acc_id);if (status != PJ_SUCCESS) error_exit("Error adding account", status);    }//    发起一个呼叫    /* If URL is specified, make call to the URL. */    if (argc > 1) {pj_str_t uri = pj_str(argv[1]);status = pjsua_call_make_call(acc_id, &uri, 0, NULL, NULL, NULL);if (status != PJ_SUCCESS) error_exit("Error making call", status);    }//    循环等待    /* Wait until user press "q" to quit. */    for (;;) {char option[10];puts("Press 'h' to hangup all calls, 'q' to quit");if (fgets(option, sizeof(option), stdin) == NULL) {    puts("EOF while reading stdin, will quit now..");    break;}if (option[0] == 'q')    break;if (option[0] == 'h')    pjsua_call_hangup_all();    }    /* Destroy pjsua */    pjsua_destroy();    return 0;}


simple_pjsua.cmain函数主要流程:

这里可以分析一下它的代码及流程图:

1 、一开始是回调使用的函数,例如 on_incoming_call 当来电话的时候, pjsip 会自动去调用你写的这个函数,前提是你在初始化 pjsua 的时候设置了 on_incoming_call = & on_incoming_call
2 error_exit 退出应用所需要的操作 3 main 函数: (1) pjsua_create () 创建 pjsua 的第一步,如果是要打电话要确认 URL 是否是正确的 pjsua_verify_url (2) 初始化 pjsua pjsua_config_default (& cfg ) 来初始化配置,然后设置一些回调函数,设置日志,最后初始化 pjsua_init (& cfg , & log_cfg , NULL); (3) 创建 UDP 的传输,设置端口号 (4) 接下来就是启动 pjsua 通过 pjsua_start (); (5) 创建账户,这个是重点所在, pjsua_acc_config_default 初始化配置,然后设置相关的内容, id 对应这 url realm 是服务器的域名,还有密码和用户名,最后调用 pjsua_acc_add (& cfg , PJ_TRUE, & acc_id ); 来实现帐号的注册。 4 、打电话,上面也提到过,你打电话的话需要验证 URL 是否正确的 pjsua_verify_url 然后调用 pjsua_call_make_call 来打电话。 5 、挂电话,调用 pjsua_call_hangup_all (); 6 最后销毁, pjsua_destroy ();

更多相关文章

  1. Android(安卓)Launcher3 禁止用户拖动图标创建文件夹,控制拖动图
  2. Android(安卓)activity exported属性理解
  3. i.MX6 安装交叉编译器
  4. Android防止用户快速点击
  5. Android(安卓)6.0关于权限的问题
  6. android:nextFocus 属性介绍
  7. android telephony phone application
  8. android 自定义控件之AutoCompleteTextView邮箱后缀自动补全
  9. Android- Context理解

随机推荐

  1. win10系统蓝屏无法进入win10系统并频繁重
  2. ASA的Twice NAT解决内网无法访问映射后的
  3. C语言试题
  4. 华为防火墙与思科路由器IPsec对接参数
  5. laket-admin 是一套使用 thinkphp6 和 la
  6. 插入排序和归并排序
  7. 2021年DevOps的四大趋势
  8. always on 清理日志记录
  9. 聚类的基本介绍
  10. 企业如何建立一体化数据分析平台?还是得说