大家好,欢迎来到IT知识分享网。
PJSIP是一个包含了SIP、SDP、RTP、RTCP、STUN、ICE等协议实现的开源库。它把基于信令协议SIP的多媒体框架和NAT穿透功能整合成高层次、抽象的多媒体通信API,这套API能够很容易的一直到各种构架中,不管是桌面计算机,还是嵌入式设备等
PJSIP组织架构
PJSIP开源库中主要包含两个模块,SIP协议栈(SIP消息处理)和 媒体流处理模块(RTP包的处理)。
SIP协议栈
这个模块,开源库由底层往上层做了各个层次的封装。
pjlib是最底层的,最基础的库,它实现的是所有其他库的平台抽象与框架库(数据结构、内存分配、文件I/O、线程、线程同步等),是SIP协议栈的基石。其他所有与SIP相关的模块都是基于PJLIB来实现的。
pjlib-util则是封装了一些常用的算法,例如MD5、CRC32等,除此之外封装了一些涉及到字符串、文件格式解析操作的API,例如文本扫描,STUN和XML格式解析。
- PJMEDIA is the multimedia framework,
- PJMEDIA-CODEC is the placeholder for media codecs
pjsip-core则是SIP协议栈的核心,在该库中,包含了三个非常重要的模块,分别是SIP endpoint、SIP transaction module、SIP dialog module、transport layer。后续会着重介绍前三个模块。
pjsip-simple则是SIP事件与出席框架,如果你程序中要实现出席制定,则该库是必备的。
pjsip-ua是INVITE会话的高层抽象(使用公共/基本对话框框架),使用该套API比较容易创建一个SIP会话。此外该库还实现了SIP client的注册API,以及呼叫转移功能
pjsua-lib是PJSIP开源库中能够使用到的最高层次抽象API,该库是基于pjsip-ua及以下库做了高层次的分装。
SIP endpoint是整个协议栈模块的核心,一般来说,一个进程内只能创建一个SIP endpoint。因此SIP endpoint是基于PJSIP开发的应用程序内所有SIP objects的管理与拥有者。
sip_endpoint的主要功能:
1.为所有的SIP对象管理内存池的分配与释放(在基于pjsip的应用程序中,动态内存的分配是在内存池基础上进行的)。
2.接收来自于传输层的消息,并将消息分配到上层,这里的上层指的是图中的SIP transaction module、SIP dialog module、application module。优先级顺序是SIP transaction module > SIP dialog module > application module。如果消息被上层接收处理,则消息由接收的那层继续往上传递处理。例如,SIP endpoint收到的SIP消息,会先较低给SIP transaction module,如果SIP transaction module在transaction hash table中找到消息所对应的transaction,则SIP transaction module在完成相应的处理后,会将消息尝试继续往上传递;如果SIP transaction module在transaction hash table中没有找到消息所对应的transaction,则该SIP消息由SIP endpoint继续往上传递。当SIP消息不能被上层所有module处理,则该消息由SIP endpoint来做默认处理。
3.SIP endpoint负责管理模块(module),module在这里是对该库进行扩展的一种方法,在代码里代表的是一个结构体数据,上面会定义module名字、优先级、以及一些函数指针。开发者可以自己定义一些优先级高于SIP transaction module的module来截获对SIP消息的处理。
4.它为所有对象和分发事件提供单个轮询函数。
transport layer: sip消息的接收与发送模块,目前支持TCP、UDP、TLS三种方式。
媒体流处理模块
该模块主要包含两部分,一是media transport,负责接收媒体流;二是媒体端口(media port)框架,该框架实现了各种媒体端口,每一个media port上定义各种操作(创建、销毁、get/put等),常用媒体端口有:File writer(记录媒体文件),File player(播放媒体文件)、stream port 、conference port(可以实现多方通话)、master port等。
media transport目前支持RTP(UDP)、SRTP(加密)、ICE(NAT穿透)
当SIP会话建立后,底层的媒体流处理流程可参考下图:
上图中四个主要组件:
- a Sound Device Port which is a thin wrapper for the Sound Device Abstraction to translate sound device’s rec_cb()/play_cb() callbacks into call to media port’s pjmedia_port_put_frame()/pjmedia_port_get_frame().
- a Conference Bridge,
- a Media Stream that is created for the call,
- a Media Transport that is attached to the Media Stream to receive/transmit RTP/RTCP packets.
从右往左,依次是media transport、stream port、conference port、sound device port、sound device 。前四个需要自己在程序里创建,最后一个sound device 是与sound device port相关联的,创建sound device port的时候便会关联到sound device。媒体流数据是通过各个media port操作进行传递的,在上图中驱动媒体流由左往右流动的“驱动器是”sound device port,该端口是通过sound device的硬件驱动不停向与它连接的media port实施/get or put fram 操作,从而媒体流得以流动。
在媒体流处理模块中,像sound device port的端口,我们称为主动型端口或者驱动型端口。媒体流处理模块中另外一个主动型端口就是master port。
在上图中最重要的是stream port ,如果你使用了pjmedia库,则必少不了stream port 。在stream port 中,从接收RTP包的角度讲,RTP包会被做一下处理:
decode RTP into frame —> put each frame into jitter buffer —> decode frame into samples with codec
从发送RTP包的角度讲,除了包含媒体流数据的RTP包外,还会存在keep alive UDP pakcet。
stream port 与media transport之间的连接是通过attach和detach操作完成的,该操作是在创建stream port执行。除此之外,为了能正常接收RTP流,我们需要为media transport提供轮询机制,通常我们使用SIP endpoint的I\O queue即可,这个是在创建media transport时通过参数设置的。
注:* jitter buffer是一种缓冲技术,主要用于音频视频流的缓冲处理。
-
媒体传输端口(pjmedia_transport)~~PJMEDIA的基础概念
Enumerations
enum pjmedia_tranport_media_option {
指定此标志时,传输将不执行媒体传输验证,这在将传输与其他传输堆叠在一起时很有用,例如,当传输UDP堆叠在传输SRTP下时,媒体传输验证仅需要由传输SRTP完成。
PJMEDIA_TPMED_NO_TRANSPORT_CHECKING = 1,
//指定此标志后,传输将允许将RTP和RTCP进行复用,即,如果远程同意,将使用与RTP相同的套接字发送RTCP。
PJMEDIA_TPMED_RTCP_MUX = 2
}
enum pjmedia_transport_type {
PJMEDIA_TRANSPORT_TYPE_UDP,//使用标准UDP的媒体传输
PJMEDIA_TRANSPORT_TYPE_ICE,//使用ICE进行媒体传输
PJMEDIA_TRANSPORT_TYPE_SRTP,//媒体传输SRTP,此传输实际上是安全适配器,可以与其他传输堆叠在一起,以对基础传输进行加密。
PJMEDIA_TRANSPORT_TYPE_LOOP,//回送媒体传输
PJMEDIA_TRANSPORT_TYPE_USER//开始用户定义的传输
}
Functions
//初始化运输信息。
void pjmedia_transport_info_init (pjmedia_transport_info *info)
//从指定的传输和所有基础传输(如果有)中获取媒体传输信息。传输中还包含有关套接字信息的信息,这些信息描述了传输的本地地址,例如,需要填写本地SDP的“ c =“和“ m =””行。
pj_status_t pjmedia_transport_get_info (pjmedia_transport *tp, pjmedia_transport_info *info)
//实用程序API,可从指定的媒体传输信息中获取特定于传输类型的信息。
void * pjmedia_transport_info_get_spc_info (pjmedia_transport_info *info, pjmedia_transport_type type)
//收到要接收的RTP / RTCP数据包时,附加要调用的回调。这只是一个简单的包装程序,attach2()如果已实现,则调用attach()传输器的成员,否则调用传输器的成员。
pj_status_t pjmedia_transport_attach2 (pjmedia_transport *tp, pjmedia_transport_attach_param *att_param)
//收到要接收的RTP / RTCP数据包时,附加要调用的回调。这只是一个简单的包装,它调用attach()了传输的成员。
pj_status_t pjmedia_transport_attach (pjmedia_transport *tp, void *user_data, const pj_sockaddr_t *rem_addr, const pj_sockaddr_t *rem_rtcp, unsigned addr_len, void(*rtp_cb)(void *user_data, void *pkt, pj_ssize_t), void(*rtcp_cb)(void *usr_data, void *pkt, pj_ssize_t))
//从传输中分离回调。这只是一个简单的包装,它调用detach()了传输的成员。分离传输之后,它将忽略传入的RTP / RTCP数据包,并拒绝发送传出的RTP / RTCP数据包。传输断开后,应用程序可以将媒体传输重新连接到另一个传输用户(例如流)。
void pjmedia_transport_detach (pjmedia_transport *tp, void *user_data)
//使用指定的媒体传输发送RTP数据包。这只是一个简单的包装,它调用send_rtp()了传输的成员。RTP数据包将传递到在pjmedia_transport_attach()函数中指定的目标地址。
pj_status_t pjmedia_transport_send_rtp (pjmedia_transport *tp, const void *pkt, pj_size_t size)
//使用指定的媒体传输发送RTCP数据包。这只是一个简单的包装,它调用send_rtcp()了传输的成员。RTCP数据包将被传递到pjmedia_transport_attach()函数中指定的目标地址。
pj_status_t pjmedia_transport_send_rtcp (pjmedia_transport *tp, const void *pkt, pj_size_t size)
//使用指定的媒体传输发送RTCP数据包。这只是一个简单的包装,它调用send_rtcp2()了传输的成员。RTCP数据包将传递到在参数addr中指定的目标地址,如果addr为NULL,则RTCP数据包将传递到在pjmedia_transport_attach()函数中指定的目标地址。
pj_status_t pjmedia_transport_send_rtcp2 (pjmedia_transport *tp, const pj_sockaddr_t *addr, unsigned addr_len, const void *pkt, pj_size_t size)
//为新的媒体会话准备媒体传输,应用程序必须在使用此传输开始新的媒体会话之前调用此函数。这只是一个简单的包装,它调用传输的media_create()成员。
pj_status_t pjmedia_transport_media_create (pjmedia_transport *tp, pj_pool_t *sdp_pool, unsigned options, const pjmedia_sdp_session *rem_sdp, unsigned media_index)
//将运输专用信息放入SDP。可以调用此功能以将传输特定信息放入初始或后续SDP提供或应答中。这只是一个简单的包装,它调用传输的encode_sdp()成员。
pj_status_t pjmedia_transport_encode_sdp (pjmedia_transport *tp, pj_pool_t *sdp_pool, pjmedia_sdp_session *sdp, const pjmedia_sdp_session *rem_sdp, unsigned media_index)
//使用本地和远程SDP中的设置启动传输会话。此功能完成的实际工作取决于基础传输类型。对于SRTP,这将根据在SDP中找到的密钥来激活加密和解密。对于ICE,这将根据SDP中的信息开始ICE协商。这只是一个简单的包装,它调用传输的media_start()成员。
pj_status_t pjmedia_transport_media_start (pjmedia_transport *tp, pj_pool_t *tmp_pool, const pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index)
//会话停止时应调用此API,以允许媒体传输释放其用于会话的资源。这只是一个简单的包装,它调用传输的media_stop()成员。
pj_status_t pjmedia_transport_media_stop (pjmedia_transport *tp)
//封闭媒体传输。这只是一个简单的包装,它调用destroy()了传输的成员。此功能将释放此传输创建的所有资源(例如套接字,内存等)。
pj_status_t pjmedia_transport_close (pjmedia_transport *tp)
//模拟沿指定方向丢失的数据包(用于测试)。启用后,传输将随机将数据包丢弃到指定的方向。
pj_status_t pjmedia_transport_simulate_lost (pjmedia_transport *tp, pjmedia_dir dir, unsigned pct_lost)
IT知识分享网
媒体传输端口用于通过网络发送/接收媒体包。媒体传输端口接口可以通过扩展来支持发送/接收数据包的不同类型传输端口。
媒体传输端口声明成pjmedia_transport“类”,类中接口部分功能在结构pjmedia_transport_op中定义。
对于媒体传输端口的使用者(通常的使用者是媒体流),传输端口的“方法”被封装在pjmedia_transport_attach()函数,所以不需要直接调用在pjmedia_transport_op结构内的函数指针。
媒体流和媒体传输端口的连接方式见下图:
基础的媒体传输端口用法
通常媒体传输端口的生命周期包括以下步骤。
- 创建媒体传输端口
应用程序在需要建立媒体会话时创建到远端的媒体传输端口。使用特定函数创建特殊作用的媒体传输端口;例如:对于UDP媒体传输端口,它使用pjmedia_transport_udp_create()或pjmedia_transport_udp_create2()函数。不同的媒体传输端口的创建使用各自的API。
另外,应用程序首次启动后要创建多个媒体传输端口池,这样做应该比较好,因为应用程序在发送初始会话建立请求(例如SIP的INVITE请求)中已经指定了RTP端口,但如果应用程序创建媒体传输端口的时间晚于建立媒体连接时(通常当发到200/OK或发到18x早期媒体),可能发生设定的RTP端口被其它程序占用的麻烦。
- 附加和使用媒体传输端口
应用程序在创建媒体会话(pjmedia_session_create())时指定媒体传输端口,它将传输端口实例做为参数传输给pjmedia_stream_create()函数,直接使用创建媒体流。(注:媒体会话是应用于两个终端之间通信的高级别抽象,它将包含一个以上的媒体流,如音频流和视频流)。
当流创建后,它将使用pjmedia_transport_attach()函数,将自身“附加”到媒体传输端口。那个在媒体传输端口的“虚函数指针”(pjmedia_transport_op)中称作“attach()”方法的封装。除此以外,流将给传输端口指定两个回调函数:一个函数用于收到RTP包后的回调,另一个用于收到RTCP包后的回调。pjmedia_transport_attach()函数也会建立外送RTP和RTCP包的目标端口。
当流需要发送外送RTP/RTCP包中,它将调用媒体传输端口API中的pjmedia_transport_send_rtp()和pjmedia_transport_send_rtcp()函数,那个在媒体传输端口的“虚函数指针”(pjmedia_transport_op)中称作“send_rtp()”和“send_rtcp()”方法的简单封装。
当流被销毁后,它将调用pjmedia_transport_detach()函数从媒体传输端口脱离。那个在媒体传输端口的“虚函数指针”(pjmedia_transport_op)中称作“detach()”方法的简单封装。当传输端口从它的使用者(流)脱离后,将不再将到达的RTP/RTCP包通告给流。同时也因目标端口被清理而拒绝发送外送的包。
- 重新使用媒体传输端口
当传输端口被脱离后,应用程序可以重新附加传输端口给其它想使用它的流。脱离流和重新附加流,好过关闭和重新打开传输端口,这样更有效率(套接字不需要关闭和重新打开)。当然,应用程序可以选择使用它的恰当方式。
- 销毁媒体传输端口
最后,如果应用程序不需要媒体传输端口时,可以调用pjmedia_transport_close()函数,那个在媒体传输端口的“虚函数指针”(pjmedia_transport_op)中称作“destroy ()”方法的封装。此函数释放所有传输端口使用的资源,如套接字和内存。
与SDP通告/应答的交互
对于基础UDP传输端口,媒体传输端口使用上述“基础媒体传输端口用法”大概足够用了。然而,更复杂的媒体传输端口,如安全RTP(SRTP)媒体传输端口和ICE媒体传输端口需要更方便地与SDP通告和应答协商互通。
媒体传输端口将通过下列函数与SDP通告和应答协商互通:
– pjmedia_transport_media_create(),用于为新媒体会话初始化媒体传输端口
– pjmedia_transport_encode_sdp(),用于编码SDP通告和应答
– pjmedia_transport_media_start(),用于激活SDP通告和应答已经协商后的设备
– pjmedia_transport_media_stop(),用于终结媒体传输端口和复位传输端口到空闲状态。
这些API的用法在SDP通告应答的上下文描述。
为新会话初始化传输端口
应用程序必须在一个新会话之前调用pjmedia_transport_media_create()函数。
创建SDP通告/应答
函数pjmedia_transport_encode_sdp()用于在SDP发送和与远程SDP协商之前,将传输端口的附加信息给本地SDP。当创建通告时,使用本地SDP(同时rem_sdp为NULL)调用pjmedia_transport_encode_sdp()。媒体传输端口将在本地SDP中添加相关属性,应用程序用将给定的本地SDP给INVATE会话,发送到无端代理。
在创建新应答时,也要调用pjmedia_transport_encode_sdp(),但此时需要将本地和远端SDP传递给函数。如果当前的远端通告中与传输端口功能相关属性变化,媒体传输端口将再一次修改本地SDP并添加相关属性给本地SDP。如果远端SDP不包含相关属性,则该会话的传输端口功能不会激活。
当应用程序发送随后的SDP通告或应用时,函数pjmedia_transport_encode_sdp()也将被调用。媒体传输端口将依据会话状态编码相关属性。
通告/应答完成
使用SDP协商状态机(通告/应答模型,RFC3264)(正常情况下是PJSIP INVATE会话部分),一旦本地和远端SDP协商完成,应用程序将传递本地和远端SDP给pjmedia_transport_media_start(),在会话中激活设置。此函数被最初和后面的SDP协商所调用。
停止传输端口
一旦会话停止,应用程序必须调用pjmedia_transport_media_stop()函数,停止传输端口功能。应用程序可以使用以下函数为后续的媒体会话重新使用传输端口:pjmedia_transport_media_create()、pjmedia_transport_encode_sdp()、pjmedia_transport_media_start()、pjmedia_transport_media_stop()。
实现传输端口
为了实现一种新的媒体传输的,需要“继承”媒体传输“类”(pjmedia_transport通过在媒体传输“界面”(以下简称“办法”)pjmedia_transport_op),并提供了一个函数来创建这个新类型传输方式(类似于pjmedia_transport_udp_create()函数)。
媒体传输预计将独立运行,也就是说,不应有像轮询传输传入的RTP / RTCP数据包这样的函数。通常可以通过将媒体套接字注册到媒体端点的I/OQueue来完成,这可以在传入数据包到达时通知传输。
或者,媒体传输可以在内部利用线程来等待传入的数据包。每当接收到数据包时,线程将调用其用户(流)提供的适当的RTP或RTCP回调。如果传输的用户是流,则该流提供的回调将是线程安全的,因此传输可以调用这些回调,而不必使用某些互斥保护来序列化访问。但是媒体传输可能仍然必须使用互斥保护来保护其内部数据,因为它可能被应用程序的线程调用(例如,发送RTP / RTCP数据包)。
-
基于PJSIP的VOIP程序开发
1. 创建module,目的处理来自于SIP UAS的invite 请求,因为该invite 请求初次被处理的时候,它不属于任何Dialog或者transaction。所以由自己创建的application module 处理。
IT知识分享网static pjsip_module mod_pjsip =
{
NULL, NULL, /* prev, next. */
{ "mod-pjsip", 9 }, /* Name. */
-1, /* Id */
PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
NULL, /* load() */
NULL, /* start() */
NULL, /* stop() */
NULL, /* unload() */
&on_rx_request, /* on_rx_request() */
NULL, /* on_rx_response() */
NULL, /* on_tx_request. */
NULL, /* on_tx_response() */
NULL, /* on_tsx_state() */
};
2 . 初始化: (一下代码仅供参考,如有疑问邮件联系vslinux@qq.com)
PJLIB-UTIL初始化 —> 创建 pool factory —> 创建SIP endpoint —> 创建SIP transport —> 初始化transaction layer —> 初始化UA layer —> 初始化 100rel module(处理临时响应) —> 创建invite session module —> 创建media endpoint —> 创建media transport
/* Then init PJLIB-UTIL: */
status = pjlib_util_init();
PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
/* Must create a pool factory before we can allocate any memory. */
pj_caching_pool_init(&sip_config.cp, &pj_pool_factory_default_policy, 0);
sip_config.pool = pj_pool_create(&sip_config.cp.factory, "pjsip-app", 1000, 1000, NULL);
/* Create global endpoint: */
{
const pj_str_t *hostname;
const char *endpt_name;
/* Endpoint MUST be assigned a globally unique name.*/
hostname = pj_gethostname();
endpt_name = hostname->ptr;
/* Create the endpoint: */
status = pjsip_endpt_create(&sip_config.cp.factory, endpt_name,
&sip_config.g_endpt);
PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
}
for (i=0; i<sip_config.thread_count; ++i) {
pj_thread_create( sip_config.pool, "app", &sip_worker_thread, NULL,
0, 0, &sip_config.sip_thread[i]);
}
/*
* Add UDP transport, with hard-coded port
* Alternatively, application can use pjsip_udp_transport_attach() to
* start UDP transport, if it already has an UDP socket (e.g. after it
* resolves the address with STUN).
* */
{
/* ip address of localhost */
pj_sockaddr addr;
pj_sockaddr_init(AF, &addr, NULL, (pj_uint16_t)SIP_PORT);
if (AF == pj_AF_INET()) {
status = pjsip_udp_transport_start( sip_config.g_endpt, &addr.ipv4, NULL,
1, &sip_config.tp);
} else if (AF == pj_AF_INET6()) {
status = pjsip_udp_transport_start6(sip_config.g_endpt, &addr.ipv6, NULL,
1, &sip_config.tp);
} else {
status = PJ_EAFNOTSUP;
}
if (status != PJ_SUCCESS) {
app_perror(THIS_FILE, "Unable to start UDP transport", status);
return 1;
}
PJ_LOG(3,(THIS_FILE, "SIP UDP listening on %.*s:%d",
(int)sip_config.tp->local_name.host.slen, sip_config.tp->local_name.host.ptr,
sip_config.tp->local_name.port));
}
/* Set transport state callback */
{
pjsip_tp_state_callback tpcb;
pjsip_tpmgr *tpmgr;
tpmgr = pjsip_endpt_get_tpmgr(sip_config.g_endpt);
tpcb = pjsip_tpmgr_get_state_cb(tpmgr);
if (tpcb != &on_tp_state_callback) {
sip_config.old_tp_cb = tpcb;
pjsip_tpmgr_set_state_cb(tpmgr, &on_tp_state_callback);
}
}
/*
* Init transaction layer.
* This will create/initialize transaction hash tables etc.
* */
status = pjsip_tsx_layer_init_module(sip_config.g_endpt);
if (status != PJ_SUCCESS) {
app_perror(THIS_FILE, "Unable to initialize transaction layer", status);
return status;
}
/*
* Initialize UA layer module.
* This will create/initialize dialog hash tables etc.
* */
status = pjsip_ua_init_module( sip_config.g_endpt, NULL );
if (status != PJ_SUCCESS) {
app_perror(THIS_FILE, "Unable to initialize UA layer", status);
return status;
}
status = pjsip_100rel_init_module(sip_config.g_endpt);
if (status != PJ_SUCCESS) {
app_perror(THIS_FILE, "Unable to initialize 100rel", status);
return status;
}
/*
* Init invite session module.
* The invite session module initialization takes additional argument,
* i.e. a structure containing callbacks to be called on specific
* occurence of events.
* We use on_media_update() callback in this application to start
* media transmission.
* */
{
/* Init the callback for INVITE session: */
pj_bzero(&sip_config.inv_cb, sizeof(sip_config.inv_cb));
sip_config.inv_cb.on_state_changed = &call_on_state_changed;
sip_config.inv_cb.on_new_session = &call_on_forked;
sip_config.inv_cb.on_media_update = &call_on_media_update;
/* Initialize invite session module: */
status = pjsip_inv_usage_init(sip_config.g_endpt, &sip_config.inv_cb);
if (status != PJ_SUCCESS) {
app_perror(THIS_FILE, "Unable to initialize invite session module", status);
return 1;
}
}
/* Register our module to receive incoming requests. */
status = pjsip_endpt_register_module( sip_config.g_endpt, &mod_pjsip);
if (status != PJ_SUCCESS) {
app_perror(THIS_FILE, "Unable to register pjsip app module", status);
return 1;
}
/*
* Initialize media endpoint.
* This will implicitly initialize PJMEDIA too.
* */
status = pjmedia_endpt_create(&sip_config.cp.factory, NULL, 1, &sip_config.g_med_endpt);
if (status != PJ_SUCCESS) {
app_perror(THIS_FILE, "Unable to create media endpoint", status);
return 1;
}
/* Add PCMA/PCMU codec to the media endpoint. */
status = pjmedia_codec_g711_init(sip_config.g_med_endpt);
if (status != PJ_SUCCESS) {
app_perror(THIS_FILE, "Unable to add codec", status);
return 1;
}
/* Create media transport used to send/receive RTP/RTCP socket.
* One media transport is needed for each call. Application may
* opt to re-use the same media transport for subsequent calls.
* */
rtp_port = (pj_uint16_t)(sip_config.rtp_start_port & 0xFFFE);
/* Init media transport for all calls. */
for (i=0, count=0; i<sip_config.max_calls; ++i, ++count) {
unsigned j;
for (j = 0; j < PJ_ARRAY_SIZE(sip_config.call[i].transport); ++j) {
/* Repeat binding media socket to next port when fails to bind
* * to current port number.
* */
int retry;
sip_config.call[i].media_index = j;
for (retry=0; retry<100; ++retry,rtp_port+=2) {
status = pjmedia_transport_udp_create3(sip_config.g_med_endpt, AF, NULL, NULL,
rtp_port, 0,
&sip_config.call[i].transport[j]);
if (status == PJ_SUCCESS) {
rtp_port += 2;
/*
* Get socket info (address, port) of the media transport. We will
* need this info to create SDP (i.e. the address and port info in
* the SDP).
* */
pjmedia_transport_info_init(&sip_config.call[i].tpinfo[j]);
pjmedia_transport_get_info(sip_config.call[i].transport[j], &sip_config.call[i].tpinfo[j]);
PJ_LOG(3,(THIS_FILE,"create media TP for call %d success!",i));
break;
}
}
}
if (status != PJ_SUCCESS) {
app_perror(THIS_FILE, "Unable to create media transport", status);
goto err;
}
}
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/10731.html