总述

  1. 软电话A 向 B 发送一个 SIP消息 INVITE, 邀请B通话
  2. 软电话B振铃,向A 回复一个SIP消息 RING, 通知 A 正在振铃中,请A等待
  3. 软电话B提机,向A发一个SIP消息 OK, 通知 A 可以通话了
  4. 软电话A 向 B 回复一个回应消息 ACK,正式启动通话
  5. 接下来,双方通话
  6. 软电话B挂机,向 A 发一个SIP消息 BYE, 通知 A 通话结束
  7. 软电话A 向 B 回复一个消息 OK, 通话结束

含有代理服务器

  • 代理服务器接收到了 INVITE 请求,然后发送了一个 100 (Trying) 响应给 Alice 的软电话。这个 100 (Trying) 响应表示这个 INVITE 已经被收到,代理正在通过路由设置路由这个 INVITE 到其目的地。
  • atlanta.com代理服务器定位到这个代理服务器在 biloxi.com,它可能执行一个特别的 DNS 查询来找到服务 biloxi.com 域的 SIP 服务器。获得 biloxi.com 代理服务器的 IP 地址,然后转发或者在这里代理其 INVITE 请求。在转发这个请求之前,这个 atlanta.com 代理服务器添加另外一个 Via 头字段,这个头字段包含自己的地址(这个 INVITE 已经在第一个 Via 包含了 Alice 的地址)
  • biloxi.com代理服务器收到这个 INVITE 消息后,然后回复一个带 100 (Trying) 响应消息到 atlanta.com 代理服务器,表示它已经收到了这个 INVITE 消息,正在处理这个请求。
  • 代理服务器会查询一个定位服务器,我们称之为定位服务,定位服务包含当前 Bob 的IP 地址。biloxi.com 代理服务器会添加另外一个 Via header ,并且携带自己的 IP 地址,这个地址是针对这个 INVITE 请求的,代理转发这个请求到 Bob 的 SIP 软电话。
  • Bob 决定是否应答这个呼叫,这里 Bob 的软电话会产生振铃提示。Bob 的软电话提示 180 振铃,这个响应消息会路由根据相反的方向回到两个代理服务器。每个代理使用 Via header 域值来决定发送响应的地址方向,并且从顶部路由记录中删除自己的地址。因此,尽管要求 DNS 和定位服务查询 路由这个初始的 INVITE 请求,180(Ringing)响应返回到呼叫方时可以没有查询消息或没有代理服务器中所保持的状态。
  • Bob 决定应答这个呼叫。当他拿起电话听筒时,他的 SIP 电话会发送一个 200 (OK) 响应消息来表示这个呼叫已经应答。这个 200 (OK) 包含了一个消息体,这个消息体带了这个呼叫会话的媒体描述类型,这个媒体描述中说明了 Bob 希望和Alice 创建会话。
  • 最后,Alice 软电话发送一个确认消息 ACK,这个消息发送到 Bob 软电话来确认最终响应 (200 (OK))已收到。在这个示例中,这个 ACK 是通过 Alice 软电话直接被发送到了 Bob 软电话,发送过程绕开了两个代理服务器。这样处理的原因就是因为两个终端已经通过互相学习知道对方的地址,双方地址是通过 INVITE/200(OK)交互时的 Contact 头获得,当然这个地址在初始时的 INVITE 是双方都不知道的。两个代理服务器的查询服务也不需要,因此,代理服务器则会退出这个呼叫流程。
  • Alice 或 Bob 任何一方都可以有权决定修改媒体会话的属性。修改会话属性是通过发送一个 re-INVITE 消息,在此消息中包含一个新的媒体描述来实现。这个 re-INVITE 涉及到了已存在的 dialog,因此其他的参与方知道这个消息是修改了现在的会话,而不是重新建立的新会话。其他方发送一个 200(ok)接受这个修改。请求方对 200(ok)发送一个 ACK。如果其他方不能接受这个修改的话,它会发生一个错误响应,例如 488 (Not Acceptable Here),同样也接收一个 ACK 确认消息。但是,这个 re-INVITE 失败不会导致目前的呼叫失败-这个会话仍然会继续使用以前协商的属性。
  • 在呼叫结束后,Bob 首先挂机(hangs up),并且生成一个 BYE 消息。这个 BYE 会直接 路由返回到 Alice 的软电话,这里仍然绕过了代理。 Alice 确认了 BYE 接收,发送一个 200(ok),结束这个会话和 BYE 消息事务。
  • Bob 软电话基于初始化处理,在一定周期内 Bob 软电话对在biloxi.com 的服务器发送 REGISTER 消息,我们称之为 SIP registrar 或者 SIP 注册。REGISTER 消息关联 Bob 的 SIP 软电话或者 SIPS URI (sip:bob@biloxi.com),这个机器是当前 Bob 写入记录的地址(它在 Contact 头中传输 SIP 或者 SIP URL)。 这个注册会写入此关联,也被称之为在数据库中的绑定或者定位服务,此定位服务可以使用在biloxi.com 域的代理中。经常,对于一个域的注册服务器需要和这个域的代理协同工作。这里一定要注意,区分不同类型的 SIP 服务器功能概念是非常重要的,它们区别是在于逻辑处理的不同,而不是物理上,形体上的不同

Structure of the Protocol

  1. SIP 结构的最低层是语法和解码层。解码是通过增强的 Backus-Naur Form grammar(BNF)语法来实现的。
  2. 第二层是传输层。它定义了用户如何发送请求,如何接收响应和服务器如何通过网络接收请求和发送响应。所有 SIP 网元都包含一个传输层。
  3. 第三层是事务层。事务是 SIP 的基础核心模块。事务是一个由用户端事务对服务器端事务发送的请求,用户端使用传输层对服务器端发送事务请求,所有的服务器端事务所携带响应消息返回到客户端。事务层处理应用层的重传,对请求响应的匹配和应用层超时管理。 任何由用户代理(UAC)完成的任务通过使用一系列的事务来触发。 用户代理包含了一个事务层,就像是一个状态代理。 无状态代理没有包含事务层。事务层有一个用户端模块(称之为用户事 务)和一个服务器端事务模块(称之为服务器端模块),每个模块通过各自的有限状态机来呈现,状态机来处理每个特别的请求
  4. 在事务层上面的是事务用户(TU)。每个 SIP 实体,除了无状态代理都是一个事务用户。当一个 TU 希望发送一个请求时,它会创建一个用户事务实例,然后把这个实例传递给这个请求,并且携带目的地 IP 地址,端口和传输请求。一个创建了用户事务的TU 也可以取消这个用户事务。当用户取消了一个事务时,它会请求服务器停止进一步的处理,变换到退出的状态,这个状态是这个事务初始化前的退出状态,并且生成对这个事务生成错误响应消息。 这个处理过程是通过一个 CANCEL 请求来处理,它构成了属于自己的事务,但是仅针对这个被取消的事务

术语

Outbound Proxy

它是一个代理,负责接收从客户端发出的请求,即使它可能不是一个通过 Request-URI 解析度服务器。 通常情况下,一个 UA 可以通过 outbound proxy手动配置,或通过自动配置协议进行学习。

Location Service

定位服务用来支持一个 SIP 重定位或代理服务器来获得关于被呼叫方可能存在的地址信息。它包含一个绑定的 address-of-record 列表数值,这些从从零个到多个 contact 地址。这个绑定关系可以通过多种方式来创建或者删除;此协议细节中定义了一个 REGISTER method 来更新绑定关系。

Redirect Server

重定向服务器是一个用户代理服务器,它会对接收的请求产生 3xx 响应,重新定向用户,让用户联系其他可选的 URL 列表中的 URI 地址。

Request

请求是一个由用户端发送到服务器的 SIP 消息,请求的目的是触发一个特别的操作。

Response

响应是一个由服务器端发送到用户端的 SIP 消息,其目的是说明请求发送后服务器端回复的状态。

Route Set

路由集是一组有序 SIP 或者 SIPS URI 的集和,它用来表示当发送 一个特别的请求时所经过的代理列表。 路由集通过路由头,例如 Record-Route 或者经过配置后获得。

Stateful Proxy

状态代理是一个逻辑实体,它按照规范中请求处理的流程保持用户端和服务器端之间的事务状态机的处理状态,也就是所谓的事务状态代理。状态代理的执行在第 16 章做了进一步的说明。状态代理(事务)和呼叫状态代理是不同的。

Stateless Proxy

无状态代理是一个逻辑实体,它不会保持用户端和服务器端之间的事务状态机。无状态代理前转从下游收到的每个请求,前转从上游收到的每一个响应。

报文结构

start-line
message-header
CRLF   //一个表示头结束的空行
[ message-body ]

Requests

![image-20231227103252240](/Users/huanghaochen/Library/Application Support/typora-user-images/image-20231227103252240.png)

Method: 此规范定义了六个方法: REGISTER 支持注册联系消息,INVITE,ACK,和CANCEL 支持会话创建,BYE 支持结束会话,OPTIONS 支持对服务器的能力查询。SIP 拓展中定义了其他的方法。

Responses

![image-20231227103448441](/Users/huanghaochen/Library/Application Support/typora-user-images/image-20231227103448441.png)

首行(start-line)

分请求行(Requests)和状态行(Responses)

  • 请求行: 由请求类型、请求目的地址和协议版本号构成。请求类型有:INVITE,ACK,OPTIONS,BYE,CANCEL和REGISTER。
  • 状态行: 是被叫方向主叫方返回的状态信息,如1xx,2xx,3xx,4xx,5xx,6xx。
请求类型
  • INVITE:用于发起呼叫请求。INVITE消息包括消息头和数据区两部分。INVITE 消息头包含主、被呼叫的地址,呼叫主题和呼叫优先级等信息。数据区则是关于会话媒体的信息,可由会话描述协议SDP 来实现。
  • BYE:当一个用户决定中止会话时,可以使用BYE 来结束会话。
  • OPTIONS:用于询问被叫端的能力信息,但OPTIONS 本身并不能发起呼叫。
  • ACK: 对已收到的消息进行确认应答。
  • REGISTER:用于用户向SIP服务器传送位置信息或地址信息。
  • CANCEL:取消当前的请求,但它并不能中止已经建立的连接。
状态类型

![image-20231228150803886](/Users/huanghaochen/Library/Application Support/typora-user-images/image-20231228150803886.png)

  • 1xx:临时消息:表示表示请求消息已经收到,后面将继续处理该请求。
  • 2xx:成功消息:表示请求已经被成功的理解、接受或执行。
  • 3xx:重定向消息:表示为了完成请求还需采取更进一步的动作。
  • 4xx:客户机错误:表示该请求含有语法错误或在这个服务器上不能被满足。
  • 5xx:服务器错误:表示该服务器不能处理一个明显有效的请求。
  • 6xx:全局性故障:表示该请求在任何服务器上都不能被实现。

消息头(message-header)

  • TO: 格式:TO: 显示名<接收者URI>;tag=n,显示名和tag可选。接收者URI是SIP网络种唯一标识接收终端的标识符。例:TO: Name<SIP:caller@WORK.COM>;TAG=11111TO: sip:caller@work.com
  • FROM: 给出标识会话发起者的URI。比如:FROM: sip:caller@work.com;tag=hyh8 tag是必需的。
  • CALL-ID: 用于全局唯一标识正在建立的会话的标识符。 随机数加UAC标识信息。
  • CSeq: 用于标识同一会话中不同事务的序号,通常由一个用作序号的整型数和消息类型组成。整个会话操作过程由不同的事务组成,每一事务所涉及的消息的CSeq序号必须相同。
  • Via: 为响应消息提供传输路径,当请求消息经过每一跳节点时,每一跳节点都把自身的IP地址信息放入顶层Via中。响应消息则沿着请求消息记录下的传输路径反向传输,首先移走指明自身IP地址信息的顶层消息头

消息体

SIP协议一个最主要的作用就是协商媒体信息。媒体信息通过message-body携带,基于SDP会话描述协议。对于PSTN语音编码格式,主要有G711A、G711U、G729等。

SIP协商中主叫方会带上自己支持的所有音频编码列表到被叫方,被叫方一般在回铃时从主叫支持的类型中选出一种或多种自己支持的编码,返回主叫后,双人按顺序选出第一个支持的编码。

INVITE 消息

(1)起始行(start-line):

<Method> <URI> <SIP_VERSION>
INVITE sip:some@192.168.31.131:50027 SIP/2.0
  • Method是请求方法,本例是INVITE, SIP协议规定的Method有六种: INVITE, ACK, CANCEL用于创建对话,BYE用于结束对话, REGISTER用于登记,OPTIONS用于查询服务器能力
  • URI表示所请求的用户或服务器, 也支持 “tel” URI, 本例是sip:some@192.168.31.131:50027,
  • SIP_VERSION是 SIP版本号,本例是 SIP/2.0

(2)消息头部(header)

一个请求消息头部至少要包含六个字段:Via, To, From, CSeq, Caller-ID, Max-Forwards

name : value ; value;

I. Via字段
Via: SIP/2.0/UDP 192.168.31.131:51971;rport;branch=z9hG4bKiYblddPPX
  • Via头字段保存所经过SIP网元(客户端或Proxy)的主机名或网络地址(可能还有端口号),消息中的所有Via头字段对请求消息而言,从下至上依次表示到当前所在SIP网元为止,请求消息所经过的路径;对响应消息而言,从上至下依次表示从当前网元开始,响应所应遵循的路径。

  • Via字段包含SIP协议版本以及消息传输所用的传输协议, 此例为: SIP/2.0/UDP

  • branch参数:

    • 在SIP网元(UAC或Proxy)发出或转发请求消息时,在其插入的Via字段中必须包含branch参数,该参数用于标识此请求消息所创建的事务。
    • branch 参数可以用做loop detection,这时参数必须被分成两部分:第一部分符合一般的原则(对于RFC3261,z9hG4bK),第二部分(此例为iYblddPPX)被用来实现loop detection以用来区分loop和spiral。
    • loop和spiral均指Proxy收到一个请求后转发,然后此转发的请求又重新到达该Proxy,区别是loop中请求的Request-URI以及其他影响Proxy处理的头字段均不变,而Spiral请求中这些部分必需有某个发生改变,spiral发生的典型情况是Request-URI发生改变。Proxy在插入Via字段前,其branch 参数的loop.
    • detection部分依据以下元素编码:To Tag,From Tag,Call-ID字段,Request-URI,Topmost Via字段,Cseq的序号部分(即与request method无关),以及proxy-require字段,proxy authorization字段。注意:request method不能用于计算branch参数,比如CANCEL以及非2XX response的ACK与其所cancel的request或对应的INVITE属于同一个事务,即其branch参数相同。见RFC3261 P22 P25 P39 P95 P105
II. Max-Forwards 字段
    Max-Forwards: 70
  • Max-Forwards 字段表示request到达UAS的跳数的限制。是一个整数,经过每一跳时减去一。如果Max-Forwards已经是零,可是request还没有到达目的地,则就会产生一个483(too many hops)响应
III. To字段
   To: <sip:some@192.168.31.131:50027>
  • To字段表示消息的接收者
  • To 字段可以有一个tag参数,to tag代表dialog的对等参与者(peer)。在UAC发出一个初始Dialog的请求(如INVITE)时,即发出out-of-dialog请求时,由于dialog还没有建立,不含to tag参数。当UAS收到INVITE请求时,在其发出的2xx或101-199响应中设置to tag参数,与UAC设置的From Tag参数以及Call-ID(呼叫唯一标识)一起作为一个Dialog ID(对话唯一标识,包含To tag,From Tag,Call-ID)的一个部分。RFC3261规定只有INVITE请求与2xx或101-199响应可以建立Dialog(由101-199响应创建的Dialog称为early dialog)。见RFC3261 P70
IV. From字段
   From: <sip:null@null>;tag=Prf3c3Xc
  • From字段表示消息的发送者
  • From字段必须包含tag参数,在UAC发出一个out-of-dialog请求(对话建立请求)时,必须设置一个唯一的tag参数,作为Dialog ID的一个部分。
V. Call-ID字段

邀请id

   Call-ID: cenXTa4i-1423587756904@appletekiAir
  • 是一个邀请(Invitation)或来自同一个UAC用户的所有登记请求(Registeration,包括更新登记,取消登记)以及由此产生的一组响应的唯一标识。一个邀请可以建立多个Dialog(当被叫用户有多个联系方式时),这成为Forking,因而Call-ID只是一次呼叫邀请的唯一标识,Call-ID与UAC在发出请求中设置的From Tag字段以及UAS在其相映中设置的To Tag字段三者一起作为一个Dialog-ID。
  • 在一个Dialog中,所有的requests和responses的Call-ID必须一致 同一UA的每一个register 的Call-ID必须一致。
VI. CSeq 字段
   CSeq: 1 INVITE
  • 用于在同一个Dialog中标识及排序事务(transaction)以及区分新的请求 与请求的重发。
  • CSeq包括顺序号和方法(method),方法必须和它所对应的request相匹配。对于out-of-dialog的非register request,取值任意
  • 对于dialog内的每一个新的request(如BYE,re-INVITE,OPTION),Cseq的序号加1。但是对于CANCEL,ACK除外。对于ACK而言,Cseq的序号必须与其所对应的request相同。对于CANCEL而言,Cseq的序号也必须与其cancel掉的request相同。
  • 注意:在同一个对话中的UAC和UAS分别维护自己的CSeq序号,他们发出请求的CSeq序号是不相关的。
VII. Contact 字段
Contact: <sip:null@192.168.31.131:51971;transport=UDP>
  • 对于非Register事务,Contact header field 主要提供了UAC或UAS的 直接联系SIP URI,UAC在发出的对话建立(out-of-dialog)INVITE请求的Contact字段中提供自己的直接联系SIP URI,在UAS收到该请求后在其发出响应的Contact字段中提供自己的直接联系SIP URI,这样在建立对话后,UA间可以通过对方的直接联系SIP URI绕过Proxy直接发送请求。
  • 对于Register事务,表示地址绑定中的contact address(vs. address-of-record)
  • Contact header field contains IP address and port on which the sender is awaiting further requests sent by callee. Other header fields are not important and will be not described here.
VIII. Content-Type字段
     Content-Type: application/sdp
  • 主要表示发给接收器的消息体的媒体类型。如果消息体不是空的,则Content-type header field一定要存在。如果Content-type header field存在,而消息体是空的,表明该类型的媒体流长度是0。
VIIII. Content-Length字段
    Content-Length: 215

表示消息体的长度。是十进制数。

(3)消息体(message body)

v=0            //版本号为0
o=user1 685988692 621323255 IN IP4 192.168.31.131 //建立者用户名+会话ID+版本+网络类型+地址类型+地址 
s=-            //会话名
c=IN IP4 192.168.31.131  //连接信息:网络类型+地址类型+地址
t=0 0         //会话活动时间 起始时间+终止时间
m=audio 49432 RTP/AVP 0 8 101   //媒体描述:媒体+端口+传送+格式列表
																	音频 + 端口49432 + 传输协议RTP + 格式AVP,有效负荷0(u率PCM编码) 
																	
a=rtpmap:0 PCMU/8000  //0或多个会话属性: 属性 + 有效负荷+ 编码名称 + 抽样频率。

a=rtpmap:8 PCMA/8000  // rtpmap +   0型  +  PCMU  +  8KHz 

a=rtpmap:101 telephone-event/8000

a=sendrecv  //a 可以有多个, 见SDP协议


REGISTER

allows a central server (registrar) to store the location of a SIP User-Agent.

Picture showing a typical registrar

img

REGISTER sip:10.10.1.99 SIP/2.0
CSeq: 1 REGISTER
Via: SIP/2.0/UDP 10.10.1.13:5060;
 branch=z9hG4bK78946131-99e1-de11-8845-080027608325;rport
User-Agent: MySipClient/4.0.0
From: <sip:13@10.10.1.99>
 ;tag=d60e6131-99e1-de11-8845-080027608325
Call-ID: e4ec6031-99e1
To: <sip:13@10.10.1.99>
Contact: <sip:13@10.10.1.13>;q=1
Allow: INVITE,ACK,OPTIONS,BYE,CANCEL,SUBSCRIBE,NOTIFY,REFER,MESSAGE,
 INFO,PING
Expires: 3600
Content-Length: 0
Max-Forwards: 70
  • User-Agent: indicates the SIP Client connecting; most devices will indicate here the manufacturer – product name – software version and other information, such as the MAC address
  • To: similar to From, in the case of the registration this field is usually the same as From. The tag is missing here but will be filled up by the SIP Server during the reply to the REGISTER
  • Contact: usually indicates where the reply should go to, if rport was not set
  • Expires: the desired registration duration in seconds

SIP:松散路由与严格路由-CSDN博客

OK

SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.1.30:5060;received=66.87.48.68
From: sip:sip2@iptel.org
To: sip:sip2@iptel.org;tag=794fe65c16edfdf45da4fc39a5d2867c.b713
Call-ID: 2443936363@192.168.1.30
CSeq: 63629 REGISTER
Contact: Msip:sip2@66.87.48.68:5060;transport=udp>;q=0.00;expires=120
Server: Sip EXpress router (0.8.11pre21xrc (i386/linux))
Content-Length: 0
Warning: 392 195.37.77.101:5060 "Noisy feedback tells:  
  pid=5110 req_src_ip=66.87.48.68 req_src_port=5060 in_uri=sip:iptel.org 
  out_uri=sip:iptel.org via_cnt==1"

BYE

BYE sip:info@hypotenuse.example.org SIP/2.0
Via: SIP/2.0/TCP port443.hotmail.example.com:54212;branch=z9hG4bK312bc
Max-Forwards:70
To: <sip:info@hypotenuse.example.org>;tag=63124
From: <sip:pythag42@hotmail.example.com>;tag=9341123
Call-ID: 34283291273
CSeq: 47 BYE
Content-Length: 0

应答只能由对端UA生成。如果UA收到未知的BYE请求,那么它应答回应481 Dialog/Transaction Does Not Exist

ACK

ACK sip:laplace@mathematica.example.org SIP/2.0
Via: SIP/2.0/TCP 128.5.2.1:5060;branch=z9hG4bK1834
Max-Forwards:70
To: Marquis de Laplace <sip:laplace@mathematica.example.org> ;tag=90210
From: Nathaniel Bowditch <sip:n.bowditch@salem.example.com> ;tag=887865
Call-ID: 152-45-32-N-32-23-47-W
CSeq: 3 ACK
Content-Type: application/sdp
Content-Length: ...
 
v=0
o=bowditch 2590844326 2590944532 IN IP4
s=Bearing
c=IN IP4 salem.example.org t=0 0
m=audio 32852 RTP/AVP 96 0
a=rtpmap:96 SPEEX/8000
a=rtpmap:0 PCMU/8000

​ ACK方法用于确认收到INVITE请求的最终应答。其它请求方法不需要确认。

​ ACK的CSeq序号不变,但方法描述变成ACK。这有助于UAS匹配对应的INVITE事务。

​ ACK消息可以携带application/sdp消息体。如果初始INVITE没有携带SDP信息,就允许在ACK消息中携带。如果INVITE带了SDP消息体,那么就不应该在ACK消息中携带SDP消息体。不能用ACK方法变更初始INVITE所描述的媒体信息,如果需要变更,必须使用re-INVITE或UPDATE方法。在ACK中携带SDP的方式常用于与其它协议交互的场景,特别是在发初始INVITE时不能获取媒体特征的场景。

总体

img

img

The Endpoint

数据结构

pjsip_endpoint

功能

1、pool factory

All memory allocations for the SIP components

  • pjsip_endpt_create_pool(),
  • pjsip_endpt_release_pool().

When the endpoint is created (pjsip_endpt_create()), application MUST specify

the pool factory that will be used by the endpoint. Endpoint keeps this pool

factory pointer throughout its lifetime, and will use this to create and release

memory pools. 创建之前必须指定pool factory

2、Timer Management

a single timer heap instance to manage timerss, and all timer creation and scheduling by all SIP components will be done via the endpoint.

  • pjsip_endpt_schedule_timer(),

  • pjsip_endpt_cancel_timer().

3、Transport manager instance

The transport manager has SIP transports and controls message parsing and printing.

4、Single instance of PJLIB’s ioqueue

5、Thread safe polling function

application’s threads can poll for timer and socket events

  • (pjsip_endpt_handle_events()

    check the occurrence of timer and network events

6、Manages PJSIP modules

7、Receives incoming SIP messages

It receives incoming SIP messages from transport manager and distributes the message to modules.

Module

数据结构

pjsip_module

/**
 * @defgroup PJSIP_MOD Modules
 * @ingroup PJSIP_CORE_CORE
 * @brief Modules are the primary means to extend PJSIP!
 * @{
 * Modules are the primary means to extend PJSIP. Without modules, PJSIP
 * would not know how to handle messages, and will simply discard all
 * incoming messages.
 *
 * Modules are registered by creating and initializing #pjsip_module 
 * structure, and register the structure to PJSIP with 
 * #pjsip_endpt_register_module().
 *
 * The <A HREF="/en/latest/api/pjsip/guide.html">PJSIP Developer's Guide</A>
 * has a thorough discussion on this subject, and readers are encouraged
 * to read the document for more information.
 */

/**
 * The declaration for SIP module. This structure would be passed to
 * #pjsip_endpt_register_module() to register the module to PJSIP.
 */
struct pjsip_module
{
    /** To allow chaining of modules in the endpoint. */
    PJ_DECL_LIST_MEMBER(struct pjsip_module);

    /**
     * Module name to identify the module.
     *
     * This field MUST be initialized before registering the module.
     */
    pj_str_t name;

    /**
     * Module ID. Application must initialize this field with -1 before
     * registering the module to PJSIP. After the module is registered,
     * this field will contain a unique ID to identify the module.
     */
    int id;

    /**
     * Integer number to identify module initialization and start order with
     * regard to other modules. Higher number will make the module gets
     * initialized later.
     *
     * This field MUST be initialized before registering the module.
     */
    int priority;

    /**
     * Optional function to be called to initialize the module. This function
     * will be called by endpoint during module registration. If the value
     * is NULL, then it's equal to returning PJ_SUCCESS.
     *
     * @param endpt     The endpoint instance.
     * @return          Module should return PJ_SUCCESS to indicate success.
     */
    pj_status_t (*load)(pjsip_endpoint *endpt);

    /**
     * Optional function to be called to start the module. This function
     * will be called by endpoint during module registration. If the value
     * is NULL, then it's equal to returning PJ_SUCCESS.
     *
     * @return          Module should return zero to indicate success.
     */
    pj_status_t (*start)(void);

    /**
     * Optional function to be called to deinitialize the module before
     * it is unloaded. This function will be called by endpoint during 
     * module unregistration. If the value is NULL, then it's equal to 
     * returning PJ_SUCCESS.
     *
     * @return          Module should return PJ_SUCCESS to indicate success.
     */
    pj_status_t (*stop)(void);

    /**
     * Optional function to be called to deinitialize the module before
     * it is unloaded. This function will be called by endpoint during 
     * module unregistration. If the value is NULL, then it's equal to 
     * returning PJ_SUCCESS.
     *
     * @param mod       The module.
     *
     * @return          Module should return PJ_SUCCESS to indicate success.
     */
    pj_status_t (*unload)(void);

    /**
     * Optional function to be called to process incoming request message.
     *
     * @param rdata     The incoming message.
     *
     * @return          Module should return PJ_TRUE if it handles the request,
     *                  or otherwise it should return PJ_FALSE to allow other
     *                  modules to handle the request.
     */
    pj_bool_t (*on_rx_request)(pjsip_rx_data *rdata);

    /**
     * Optional function to be called to process incoming response message.
     *
     * @param rdata     The incoming message.
     *
     * @return          Module should return PJ_TRUE if it handles the 
     *                  response, or otherwise it should return PJ_FALSE to 
     *                  allow other modules to handle the response.
     */
    pj_bool_t (*on_rx_response)(pjsip_rx_data *rdata);

    /**
     * Optional function to be called when transport layer is about to
     * transmit outgoing request message.
     *
     * @param tdata     The outgoing request message.
     *
     * @return          Module should return PJ_SUCCESS in all cases. 
     *                  If non-zero is returned, the message 
     *                  will not be sent.
     */
    pj_status_t (*on_tx_request)(pjsip_tx_data *tdata);

    /**
     * Optional function to be called when transport layer is about to
     * transmit outgoing response message.
     *
     * @param tdata     The outgoing response message.
     *
     * @return          Module should return PJ_SUCCESS in all cases. 
     *                  If non-zero is returned, the message 
     *                  will not be sent.
     */
    pj_status_t (*on_tx_response)(pjsip_tx_data *tdata);

    /**
     * Optional function to be called when this module is acting as 
     * transaction user for the specified transaction, when the 
     * transaction's state has changed.
     *
     * @param tsx       The transaction.
     * @param event     The event which has caused the transaction state
     *                  to change.
     */
    void (*on_tsx_state)(pjsip_transaction *tsx, pjsip_event *event);

};

概述

For incoming messages, the endpoint (pjsip_endpoint) distributes the message to all modules starting from module with highest priority, until one of them says that it has processed the message. For outgoing messages, the endpoint distributes the outgoing messages before they are transmitted to the wire, to allow modules to put last modification on the message if they wish.

API

endpoint调用改变模块调用:The four function pointers load, start, stop, and unload are called by endpoint to control the module state.

img

pjsip_endpt_register_module() register the module to PJSIP endpoint

on_rx_request() and on_rx_response() : endpoint按照优先级从高到低发给模块,调用这两个函数,返回值不为0,表示模块处理了,停止发送

  • module to receive SIP messages from endpoint (pjsip_endpt) or from other modules.
  • The return value of these callbacks is important. If a callback has returned non-zero (i.e. true condition), it semantically means that the module has taken care the message; in this case, the endpoint will stop distributing the message to other modules.

on_tx_request() and on_tx_response()

  • function pointers are called by transport manager before a message is transmitted.
  • This gives an opportunity for some types of modules (e.g. sigcomp, message signing) chance to make last modification to the message before transmitt.
  • All modules MUST return PJ_SUCCESS (i.e. zero status), or otherwise the transmission will be cancelled.

on_tsx_state()

receive notification every time a transaction state has changed, which can be caused by receipt of message, transmission of message, timer events, or transport error event. 每次事务状态发生变化时接收通知,这种变化可能是由消息的接收、消息的传输、计时器事件或传输错误事件引起的。

2.1.2 Module Priorities

Module priority specifies the order of which modules are called first to process the callback. Module with higher priority (i.e. lower priority number) will have their on_rx_request() and on_rx_response() called first, and on_tx_request() and on_tx_response() called last.

/**
 * Module priority guidelines.
 */
enum pjsip_module_priority
{
    /** 
     * This is the priority used by transport layer.
     */
    PJSIP_MOD_PRIORITY_TRANSPORT_LAYER  = 8,

    /**
     * This is the priority used by transaction layer.
     */
    PJSIP_MOD_PRIORITY_TSX_LAYER        = 16,

    /**
     * This is the priority used by the user agent and proxy layer.
     */
    PJSIP_MOD_PRIORITY_UA_PROXY_LAYER   = 32,

    /**
     * This is the priority used by the dialog usages.
     */
    PJSIP_MOD_PRIORITY_DIALOG_USAGE     = 48,

    /**
     * This is the recommended priority to be used by applications.
     */
    PJSIP_MOD_PRIORITY_APPLICATION      = 64
};

PJSIP_MOD_PRIORITY_TRANSPORT_LAYER

  • transport manager 的优先级
  • 低于这个的优先级,可以在transport layer之前调用on_tx_request()/on_tx_response() ;高于这个优先级,可以在transport layer处理完后继续处理
  • This priority currently is only used to control message transmission, i.e. module with lower priority than this (that means higher priority number!) will have the on_tx_request()/on_tx_response() called before the message is processed by transport layer (e.g. destination is calculated, message is printed to contiguous buffer), while module with higher priority than this will have the callback called after the message has been processed by transport layer.

PJSIP_MOD_PRIORITY_TSX_LAYER

  • transaction layer优先级

  • The transaction layer absorbs all incoming messages that belong to a transaction.

PJSIP_MOD_PRIORITY_UA_PROXY_LAYER

  • priority used by UA layer (i.e.dialog framework) or proxy layer.
  • The UA layer absorbs all incoming messages that belong to a dialog set (this means forked responses as well).

PJSIP_MOD_PRIORITY_DIALOG_USAGE

  • for dialog usages.
  • Currently PJSIP implements two types of dialog usages: invite sesssion and event subscriptionsession (including REFER subscription). The dialog usage absorbs messages inside a dialog that belong to particular session.

PJSIP_MOD_PRIORITY_APPLICATION

  • for typical application modules, when they want to utilize transactions, dialogs, and dialog usages.

2.1.3 Incoming Message Processing by Modules

img
  1. incoming message arrives, it is represented as receive message buffer (struct pjsip_rx_data, see section 5.1 “Receive Data Buffer”).
  2. Transport manager parses the message, put the parsed data structures in the receive message buffer
  3. pass the message to the endpoint.
  4. The endpoint distributes the receive message buffer to each registered module by calling on_rx_request() or on_rx_response() callback, starting from module with highest priority (i.e. lowest priority number) until one of them returns non-zero. When one of the module has returned non-zero, endpoint stops distributing the message to the remaining of the modules, because it assumes that the module has taken care about the processing of the message.
  5. The module which returns non-zero on the callback itself may further distribute the message to other modules. i.e
img

2.1.4 Outgoing Message Processing by Modules

  1. An outgoing request or response message is represented by a transmit data buffer (pjsip_tx_data), which among other things, contains the message structure itself, memory pool, contiguous buffer, and transport info.

  2. When pjsip_transport_send() is called to send a message, transport manager calls on_tx_request() or on_tx_response() for all modules, starting with modules with lowest priority (i.e. highest priority number).

  3. When these callbacks are called, the message may have or have not been processed by the transport layer. The transport layer is responsible for managing these information inside a transmit buffer:

    • transport info, and

    • printing the message structure to contiguous buffer.

      If modules want to modify the message structure before it is printed to buffer, then it must set its priority number higher than transport layer priority. If modules want to see the actual packet bytes as they are transmitted to the wire (e.g. for logging purpose), then it should set its priority number to lower than transport layer.

2.1.5 Transaction User and State Callback

  • transaction 状态变化,接收notification from a particular transaction

  • This callback is unique because transaction state may change because of non message related events (e.g. timer timeout and transport error).

  • This callback will only be called after the module has been registered as transaction user for a particular transaction. Only one transaction user is allowed per transaction. Transaction user can be set to transaction on per transaction basis.

For transactions created within a dialog, the transaction user is set to the UA layer module on behalf of a particular dialog. When applications creates the transaction manually, they may set themselves as the transaction user.

2.1.6 Module Specific Data

mod_data container:modules can put module specific data in that component

The mod_data array is indexed by module ID

Application can retrieve the value calling pjsip_rdata_get_tsx() or pjsip_rdata_get_dlg(),

2.2.1 Module Management API

pj_status_t pjsip_endpt_register_module( pjsip_endpoint *endpt,
																					pjsip_module *module );

Register a module to the endpoint. The endpoint will then call the load and start function in the module to properly initialize the module, and assign a unique module ID for the module.

pj_status_t pjsip_endpt_unregister_module( pjsip_endpoint *endpt,

																						pjsip_module *module );

Unregister a module from the endpoint. The endpoint will then call the stop and unload function in the module to properly shutdown the module.

2.2.2 Module Capabilities

Currently the endpoint manages these capabilities

  • allowed SIP methods (Allow header field),
  • supported SIP extensions (Supported header field).
  • supported content type (Accept header field).

These header fields will be added to outgoing requests or responses automatically, where appropriate.

A module declares new capability by calling pjsip_endpt_add_capability() function.

Message Elements

3.1 Uniform Resource Indicator (URI)

img

3.1.2 URI Context

URI context 指明哪里使用URI,指明什么元素可以出现在context

/**
 * URI context.
 */
typedef enum pjsip_uri_context_e
{
    PJSIP_URI_IN_REQ_URI,       /**< The URI is in Request URI. */
    PJSIP_URI_IN_FROMTO_HDR,    /**< The URI is in From/To header. */
    PJSIP_URI_IN_CONTACT_HDR,   /**< The URI is in Contact header. */
    PJSIP_URI_IN_ROUTING_HDR,   /**< The URI is in Route/Record-Route header. */
    PJSIP_URI_IN_OTHER          /**< Other context (web page, business card, etc.) */
} pjsip_uri_context_e;

3.1.3 Base URI

struct pjsip_uri
{
		pjsip_uri_vptr *vptr;
};

pjsip_uri_vptr 虚函数表,由各类url自行定义

/**
 * URI 'virtual' function table.
 * All types of URI in this library (such as sip:, sips:, tel:, and name-addr) 
 * will have pointer to this table as their first struct member. This table
 * provides polimorphic behaviour to the URI.
 */
typedef struct pjsip_uri_vptr
{
    /** 
     * Get URI scheme. 
     * @param uri the URI (self).
     * @return the URI scheme.
     */
    const pj_str_t* (*p_get_scheme)(const void *uri);

    /**
     * Get the URI object contained by this URI, or the URI itself if
     * it doesn't contain another URI.
     * @param uri the URI (self).
     */
    void* (*p_get_uri)(void *uri);

    /**
     * Print URI components to the buffer, following the rule of which 
     * components are allowed for the context.
     * @param context the context where the URI will be placed.
     * @param uri the URI (self).
     * @param buf the buffer.
     * @param size the size of the buffer.
     * @return the length printed.
     */
    pj_ssize_t (*p_print)(pjsip_uri_context_e context,
                          const void *uri, 
                          char *buf, pj_size_t size);

    /** 
     * Compare two URIs according to the context.
     * @param context the context.
     * @param uri1 the first URI (self).
     * @param uri2 the second URI.
     * @return PJ_SUCCESS if equal, or otherwise the error status which
     *              should point to the mismatch part.
     */
    pj_status_t (*p_compare)(pjsip_uri_context_e context, 
                             const void *uri1, const void *uri2);

    /** 
     * Clone URI. 
     * @param pool the pool.
     * @param the URI to clone (self).
     * @return new URI.
     */
    void *(*p_clone)(pj_pool_t *pool, const void *uri);

} pjsip_uri_vptr;

以下的一些函数可以应用在所有类型的url上作为pjsip_uri_vptr虚函数表的初始化

const pj_str_t\* pjsip_uri_get_scheme( const pjsip_uri \*uri );

Get the URI scheme string (e.g. “sip”, “sips”, “tel”, etc.).

pjsip_uri\* pjsip_uri_get_uri( pjsip_uri \*uri );

Get the URI object. Normally all URI objects will return itself except name address which will return the URI inside the name address object.

pj_status_t pjsip_uri_cmp( pjsip_uri_context_e context, 
											const pjsip_uri \*uri1, 
											const pjsip_uri \*uri2);

Compare uri1 and uri2 according to the specified context. Parameters which are not allowed to appear in the specified context will be ignored in the comparison. It will return PJ_SUCCESS is both URIs are equal.

int pjsip_uri_print( 
		pjsip_uri_context_e context, 
		const pjsip_uri \*uri,
		char \*buffer, 
		pj_size_t max_size);

Print uri to the specified buffer according to the specified context. Parameters which are not allowed to appear in the specified context will not be included in the printing.

pjsip_uri* pjsip_uri_clone( pj_pool_t *pool, const pjsip_uri *uri );

Create a deep clone of uri using the specified pool.

3.1.4 SIP and SIPS URI

/**
 * @}
 */

/**
 * @defgroup PJSIP_SIP_URI SIP URI Scheme and Name address
 * @ingroup PJSIP_URI
 * @brief SIP URL structure ("sip:" and "sips:")
 * @{
 */


/**
 * SIP and SIPS URL scheme.
 */
typedef struct pjsip_sip_uri
{
    pjsip_uri_vptr *vptr;               /**< Pointer to virtual function table.*/
    pj_str_t        user;               /**< Optional user part. */
    pj_str_t        passwd;             /**< Optional password part. */
    pj_str_t        host;               /**< Host part, always exists. */
    int             port;               /**< Optional port number, or zero. */
    pj_str_t        user_param;         /**< Optional user parameter */
    pj_str_t        method_param;       /**< Optional method parameter. */
    pj_str_t        transport_param;    /**< Optional transport parameter. */
    int             ttl_param;          /**< Optional TTL param, or -1. */
    int             lr_param;           /**< Optional loose routing param, or zero */
    pj_str_t        maddr_param;        /**< Optional maddr param */
    pjsip_param     other_param;        /**< Other parameters grouped together. */
    pjsip_param     header_param;       /**< Optional header parameter. */
} pjsip_sip_uri;

SIP and SIPS URI类型URL 特有函数

pjsip_sip_uri* pjsip_sip_uri_create( pj_pool_t *pool, pj_bool_t secure );

Create a new SIP URL using the specified pool. If the secure flag is set to non-zero, then SIPS URL will be created. This function will set vptr member of the URL to SIP or SIPS vptr and set all other members to blank value.

void pjsip_sip_uri_init( pjsip_sip_uri \*url, pj_bool_t secure );

Initialize a SIP URL structure.

void pjsip_sip_uri_assign( pj_pool_t \*pool, 
													pjsip_sip_uri \*url, 
													const pjsip_sip_uri \*rhs );

Perform deep copy of rhs to url.

3.1.5 Tel URI

pjsip_tel_uri\* pjsip_tel_uri_create( pj_pool_t \*pool );

Create a new tel: URI.

int pjsip_tel_nb_cmp( const pj_str_t \*nb1, const pj_str_t \*nb2 );

This utility function compares two telephone numbers for equality, according to rules specified in RFC 3966 (about tel: URI). It recognizes global and local numbers, and it ignores visual separators during the comparison.

3.1.6 Name Address

/**
 * SIP name-addr, which typically appear in From, To, and Contact header.
 * The SIP name-addr contains a generic URI and a display name.
 */
typedef struct pjsip_name_addr
{
    /** Pointer to virtual function table. */
    pjsip_uri_vptr  *vptr;

    /** Optional display name. */
    pj_str_t         display;

    /** URI part. */
    pjsip_uri       *uri;

} pjsip_name_addr;

pjsip_name_addr\* pjsip_name_addr_create( pj_pool_t \*pool );

Create a new name address. This will set initialize the virtual function table pointer, set blank display name and set the uri member to NULL.

void pjsip_name_addr_assign( pj_pool_t \*pool, 

															pjsip_name_addr \*name_addr, 

															const pjsip_name_addr \*rhs );

Copy rhs to name_addr.

3.2 SIP Methods

3.2.1 SIP Method Representation (pjsip_method)

/**
 * This structure represents a SIP method.
 * Application must always use either #pjsip_method_init or #pjsip_method_set
 * to make sure that method name is initialized correctly. This way, the name
 * member will always contain a valid method string regardless whether the ID
 * is recognized or not.
 */
struct pjsip_method
{
    pjsip_method_e id;      /**< Method ID, from \a pjsip_method_e. */
    pj_str_t       name;    /**< Method name, which will always contain the 
                                 method string. */
};

  • 可扩展

  • 对于sip协议定义的头在id字段,填入以下枚举类型

    /**
     * This enumeration declares SIP methods as described by RFC3261. Additional
     * methods do exist, and they are described by corresponding RFCs for the SIP
     * extentensions. Since they won't alter the characteristic of the processing
     * of the message, they don't need to be explicitly mentioned here.
     */
    typedef enum pjsip_method_e
    {
        PJSIP_INVITE_METHOD,    /**< INVITE method, for establishing dialogs.   */
        PJSIP_CANCEL_METHOD,    /**< CANCEL method, for cancelling request.     */
        PJSIP_ACK_METHOD,       /**< ACK method.                                */
        PJSIP_BYE_METHOD,       /**< BYE method, for terminating dialog.        */
        PJSIP_REGISTER_METHOD,  /**< REGISTER method.                           */
        PJSIP_OPTIONS_METHOD,   /**< OPTIONS method.                            */
    
        PJSIP_OTHER_METHOD      /**< Other method.                              */
    
    } pjsip_method_e;
    

3.2.2 SIP Method API

void pjsip_method_init( 

​							pjsip_method \*method, pj_pool_t \*pool,

​							const pj_str_t \*method_name );

Initialize method from string. This will initialize the id of the method field to the correct value.

void pjsip_method_init_np( pjsip_method \*method,

​							pj_str_t \*method_name );

Initialize method from method_name string without duplicating the string (np stands for no pool). The id field will be initialize accordingly.

void pjsip_method_set( pjsip_method \*method, 

​							pjsip_method_id_e method_id );

Initialize method from the method ID enumeration. The name field will be initialized accordingly.

void pjsip_method_copy( 

​							pj_pool_t \*pool, 

​							pjsip_method \*method, 

​							const pjsip_method \*rhs );

Copy rhs to method.

int pjsip_method_cmp( 

​							const pjsip_method \*method1,

​							const pjsip_method \*method2 );

Compare method1 to method2 for equality. This function returns zero if both methods are equal, and (-1) or (+1) if method1 is less or greater than method2 respectively.

3.3 Header Fields

3.3.1 Header “Class Diagram”

img

3.3.2 Header Structure

所有类型header共有的属性 **PJSIP_DECL_HDR_MEMBER(hdr)**宏保证共有属性

/**
 * Generic fields for all SIP headers are declared using this macro, to make
 * sure that all headers will have exactly the same layout in their start of
 * the storage. This behaves like C++ inheritance actually.
 */
#define PJSIP_DECL_HDR_MEMBER(hdr)   \
    /** List members. */        \
    PJ_DECL_LIST_MEMBER(hdr);   \
    /** Header type */          \
    pjsip_hdr_e     type;       \
    /** Header name. */         \
    pj_str_t        name;       \
    /** Header short name version. */   \
    pj_str_t        sname;              \
    /** Virtual function table. */      \
    pjsip_hdr_vptr *vptr

base header

/**
 * Generic SIP header structure, for generic manipulation for headers in the
 * message. All header fields can be typecasted to this type.
 */
struct pjsip_hdr
{
    PJSIP_DECL_HDR_MEMBER(struct pjsip_hdr);
};

pjsip_hdr_vptr通用虚函数表

/**
 * This structure provides the pointer to basic functions that are needed
 * for generic header operations. All header fields will have pointer to
 * this structure, so that they can be manipulated uniformly.
 */
typedef struct pjsip_hdr_vptr
{
    /** 
     * Function to clone the header. 
     *
     * @param pool  Memory pool to allocate the new header.
     * @param hdr   Header to clone.
     *
     * @return A new instance of the header.
     */
    void *(*clone)(pj_pool_t *pool, const void *hdr);

    /** 
     * Pointer to function to shallow clone the header. 
     * Shallow cloning will just make a memory copy of the original header,
     * thus all pointers in original header will be kept intact. Because the
     * function does not need to perform deep copy, the operation should be
     * faster, but the application must make sure that the original header
     * is still valid throughout the lifetime of new header.
     *
     * @param pool  Memory pool to allocate the new header.
     * @param hdr   The header to clone.
     */
    void *(*shallow_clone)(pj_pool_t *pool, const void *hdr);

    /** Pointer to function to print the header to the specified buffer.
     *  Returns the length of string written, or -1 if the remaining buffer
     *  is not enough to hold the header.
     *
     *  @param hdr  The header to print.
     *  @param buf  The buffer.
     *  @param len  The size of the buffer.
     *
     *  @return     The size copied to buffer, or -1 if there's not enough space.
     */
    int (*print_on)(void *hdr, char *buf, pj_size_t len);

} pjsip_hdr_vptr;

以下的一些函数可以应用在所有类型的header上作为pjsip_hdr_vptr虚函数表的初始化

pjsip_hdr *pjsip_hdr_clone( pj_pool_t *pool, const pjsip_hdr *hdr );

Perform deep clone of hdr header.

pjsip_hdr *pjsip_hdr_shallow_clone( pj_pool_t *pool, const pjsip_hdr *hdr );

Perform shallow clone of hdr header. A shallow cloning creates a new exact copy of the specified header field, however most of its value will still point to the values in the original header. Normally shallow clone is just a simple memcpy() from the original header to a new header, therefore it’s expected that this operation is faster than deep cloning. However, care must be taken when shallow cloning headers. It must be understood that the new header still shares common pointers to the values in the old header. Therefore, when the pool containing the original header is destroyed, the new header will be rendered invalid too although the new header was shallow-cloned using different memory pool. Or if some values in the original header was modified, then the corresponding values in the shallow-cloned header will be modified too.Despite of this, shallow cloning is used widely in the library. For example, a dialog has some headers which values are more or less persistent during the session (e.g. From, To, Call-Id, Route, and Contact). When creating a request, the dialog can just shallow-clone these headers (instead of performing full cloning) and put them in the request message.

int pjsip_hdr_print_on( pjsip_hdr *hdr, char *buf, pj_size_t max_size);

Print the specified header to a buffer (e.g. before transmission). This function returns the number of bytes printed to the buffer, or –1 when the buffer is overflow.

3.3.4 Supported Header Fields

  • 标准头部在The “standard” PJSIP header fields are declared in <pjsip/sip_msg.h>.

  • Other header fields may be declared in header files that implement specific functionalities or SIP extensions (e.g. headers used by SIMPLE extension, etc.).

  • 创建头部The APIs to create individual header fields are by convention named after the header field name and followed by _create() suffix. For example, call function pjsip_via_hdr_create() to create an instance of pjsip_via_hdr header.

3.3.5 Header Array Elements

群组sip消息,拆分处理

When the parser encounters such arrays in headers, it will split the array into individual headers while maintaining their order of appearance.

**3.4 Message Body (pjsip_msg_body)**消息体本身

数据、类型、长度

/**
 * @addtogroup PJSIP_MSG_BODY Message Body
 * @brief SIP message body structures and manipulation.
 * @ingroup PJSIP_MSG
 * @{
 */

/**
 * Generic abstraction to message body.
 * When an incoming message is parsed (pjsip_parse_msg()), the parser fills in
 * all members with the appropriate value. The 'data' and 'len' member will
 * describe portion of incoming packet which denotes the message body.
 * When application needs to attach message body to outgoing SIP message, it
 * must fill in all members of this structure. 
 */
struct pjsip_msg_body
{
    /** MIME content type. 
     *  For incoming messages, the parser will fill in this member with the
     *  content type found in Content-Type header.
     *
     *  For outgoing messages, application may fill in this member with
     *  appropriate value, because the stack will generate Content-Type header
     *  based on the value specified here.
     *
     *  If the content_type is empty, no Content-Type AND Content-Length header
     *  will be added to the message. The stack assumes that application adds
     *  these headers themselves.
     */
    pjsip_media_type content_type;

    /** Pointer to buffer which holds the message body data. 
     *  For incoming messages, the parser will fill in this member with the
     *  pointer to the body string.
     *
     *  When sending outgoing message, this member doesn't need to point to the
     *  actual message body string. It can be assigned with arbitrary pointer,
     *  because the value will only need to be understood by the print_body()
     *  function. The stack itself will not try to interpret this value, but
     *  instead will always call the print_body() whenever it needs to get the
     *  actual body string.
     */
    void *data;

    /** The length of the data. 
     *  For incoming messages, the parser will fill in this member with the
     *  actual length of message body.
     *
     *  When sending outgoing message, again just like the "data" member, the
     *  "len" member doesn't need to point to the actual length of the body 
     *  string.
     */
    unsigned len;

    /** Pointer to function to print this message body. 
     *  Application must set a proper function here when sending outgoing 
     *  message.
     *
     *  @param msg_body     This structure itself.
     *  @param buf          The buffer.
     *  @param size         The buffer size.
     *
     *  @return             The length of the string printed, or -1 if there is
     *                      not enough space in the buffer to print the whole
     *                      message body.
     */
    int (*print_body)(struct pjsip_msg_body *msg_body, 
                      char *buf, pj_size_t size);

    /** Clone the data part only of this message body. Note that this only
     *  duplicates the data part of the body instead of the whole message
     *  body. If application wants to duplicate the entire message body
     *  structure, it must call #pjsip_msg_body_clone().
     *
     *  @param pool         Pool used to clone the data.
     *  @param data         The data inside message body, to be cloned.
     *  @param len          The length of the data.
     *
     *  @return             New data duplicated from the original data.
     */
    void* (*clone_data)(pj_pool_t *pool, const void *data, unsigned len);

};
pj_status_t pjsip_msg_body_clone( pj_pool_t \*pool,

																	pjsip_msg_body \*dst_body,

																	const pjsip_msg_body \*src_body);

Clone the message body in src_body to the dst_body. This will duplicate the contents of the message body using the clone_data member of the source message body.

3.5 Message (pjsip_msg)


/* **************************************************************************/
/**
 * @defgroup PJSIP_MSG_MSG Message Structure
 * @brief SIP message (request and response) structure and operations.
 * @ingroup PJSIP_MSG
 * @{
 */

/**
 * Message type (request or response).
 */
typedef enum pjsip_msg_type_e
{
    PJSIP_REQUEST_MSG,      /**< Indicates request message. */
    PJSIP_RESPONSE_MSG      /**< Indicates response message. */
} pjsip_msg_type_e;


/**
 * This structure describes a SIP message.
 */
struct pjsip_msg
{
    /** Message type (ie request or response). */
    pjsip_msg_type_e  type;

    /** The first line of the message can be either request line for request
     *  messages, or status line for response messages. It is represented here
     *  as a union.
     */
    union
    {
        /** Request Line. */
        struct pjsip_request_line   req;

        /** Status Line. */
        struct pjsip_status_line    status;
    } line;

    /** List of message headers. */
    pjsip_hdr hdr;

    /** Pointer to message body, or NULL if no message body is attached to
     *  this mesage. 
     */
    pjsip_msg_body *body;
};
  • request and response message in PJSIP are represented with pjsip_msg
/** 
 * Create new request or response message.
 *
 * @param pool      The pool.
 * @param type      Message type.
 * @return          New message, or THROW exception if failed.
 */
PJ_DECL(pjsip_msg*)  pjsip_msg_create( pj_pool_t *pool, pjsip_msg_type_e type);


/**
 * Perform a deep clone of a SIP message.
 *
 * @param pool      The pool for creating the new message.
 * @param msg       The message to be duplicated.
 *
 * @return          New message, which is duplicated from the original 
 *                  message.
 */
PJ_DECL(pjsip_msg*) pjsip_msg_clone( pj_pool_t *pool, const pjsip_msg *msg);


/** 
 * Find a header in the message by the header type.
 *
 * @param msg       The message.
 * @param type      The header type to find.
 * @param start     The first header field where the search should begin.
 *                  If NULL is specified, then the search will begin from the
 *                  first header, otherwise the search will begin at the
 *                  specified header.
 *
 * @return          The header field, or NULL if no header with the specified 
 *                  type is found.
 */
PJ_DECL(void*)  pjsip_msg_find_hdr( const pjsip_msg *msg, 
                                    pjsip_hdr_e type, const void *start);

/** 
 * Find a header in the message by its name.
 *
 * @param msg       The message.
 * @param name      The header name to find.
 * @param start     The first header field where the search should begin.
 *                  If NULL is specified, then the search will begin from the
 *                  first header, otherwise the search will begin at the
 *                  specified header.
 *
 * @return          The header field, or NULL if no header with the specified 
 *                  type is found.
 */
PJ_DECL(void*)  pjsip_msg_find_hdr_by_name( const pjsip_msg *msg, 
                                            const pj_str_t *name, 
                                            const void *start);

/** 
 * Find a header in the message by its name and short name version.
 *
 * @param msg       The message.
 * @param name      The header name to find.
 * @param sname     The short name version of the header name.
 * @param start     The first header field where the search should begin.
 *                  If NULL is specified, then the search will begin from the
 *                  first header, otherwise the search will begin at the
 *                  specified header.
 *
 * @return          The header field, or NULL if no header with the specified 
 *                  type is found.
 */
PJ_DECL(void*)  pjsip_msg_find_hdr_by_names(const pjsip_msg *msg, 
                                            const pj_str_t *name, 
                                            const pj_str_t *sname,
                                            const void *start);

/** 
 * Find and remove a header in the message. 
 *
 * @param msg       The message.
 * @param hdr       The header type to find.
 * @param start     The first header field where the search should begin,
 *                  or NULL to search from the first header in the message.
 *
 * @return          The header field, or NULL if not found.
 */
PJ_DECL(void*)  pjsip_msg_find_remove_hdr( pjsip_msg *msg, 
                                           pjsip_hdr_e hdr, void *start);

/** 
 * Add a header to the message, putting it last in the header list.
 *
 * @param msg       The message.
 * @param hdr       The header to add.
 *
 * @bug Once the header is put in a list (or message), it can not be put in 
 *      other list (or message). Otherwise Real Bad Thing will happen.
 */
PJ_INLINE(void) pjsip_msg_add_hdr( pjsip_msg *msg, pjsip_hdr *hdr )
{
    pj_list_insert_before(&msg->hdr, hdr);
}

/** 
 * Add header field to the message, putting it in the front of the header list.
 *
 * @param msg   The message.
 * @param hdr   The header to add.
 *
 * @bug Once the header is put in a list (or message), it can not be put in 
 *      other list (or message). Otherwise Real Bad Thing will happen.
 */
PJ_INLINE(void) pjsip_msg_insert_first_hdr( pjsip_msg *msg, pjsip_hdr *hdr )
{
    pj_list_insert_after(&msg->hdr, hdr);
}

/** 
 * Print the message to the specified buffer. 
 *
 * @param msg   The message to print.
 * @param buf   The buffer
 * @param size  The size of the buffer.
 *
 * @return      The length of the printed characters (in bytes), or NEGATIVE
 *              value if the message is too large for the specified buffer.
 */
PJ_DECL(pj_ssize_t) pjsip_msg_print(const pjsip_msg *msg, 
                                    char *buf, pj_size_t size);


3.6 SIP Status Codes

sip状态码

/**
 * This enumeration lists standard SIP status codes according to RFC 3261.
 * In addition, it also declares new status class 7xx for errors generated
 * by the stack. This status class however should not get transmitted on the 
 * wire.
 */
typedef enum pjsip_status_code
{
    PJSIP_SC_NULL = 0,

    PJSIP_SC_TRYING = 100,
    PJSIP_SC_RINGING = 180,
    PJSIP_SC_CALL_BEING_FORWARDED = 181,
    PJSIP_SC_QUEUED = 182,
    PJSIP_SC_PROGRESS = 183,
    PJSIP_SC_EARLY_DIALOG_TERMINATED = 199,

    PJSIP_SC_OK = 200,
    PJSIP_SC_ACCEPTED = 202,
    PJSIP_SC_NO_NOTIFICATION = 204,

    PJSIP_SC_MULTIPLE_CHOICES = 300,
    PJSIP_SC_MOVED_PERMANENTLY = 301,
    PJSIP_SC_MOVED_TEMPORARILY = 302,
    PJSIP_SC_USE_PROXY = 305,
    PJSIP_SC_ALTERNATIVE_SERVICE = 380,

    PJSIP_SC_BAD_REQUEST = 400,
    PJSIP_SC_UNAUTHORIZED = 401,
    PJSIP_SC_PAYMENT_REQUIRED = 402,
    PJSIP_SC_FORBIDDEN = 403,
    PJSIP_SC_NOT_FOUND = 404,
    PJSIP_SC_METHOD_NOT_ALLOWED = 405,
    PJSIP_SC_NOT_ACCEPTABLE = 406,
    PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED = 407,
    PJSIP_SC_REQUEST_TIMEOUT = 408,
    PJSIP_SC_CONFLICT = 409,
    PJSIP_SC_GONE = 410,
    PJSIP_SC_LENGTH_REQUIRED = 411,
    PJSIP_SC_CONDITIONAL_REQUEST_FAILED = 412,
    PJSIP_SC_REQUEST_ENTITY_TOO_LARGE = 413,
    PJSIP_SC_REQUEST_URI_TOO_LONG = 414,
    PJSIP_SC_UNSUPPORTED_MEDIA_TYPE = 415,
    PJSIP_SC_UNSUPPORTED_URI_SCHEME = 416,
    PJSIP_SC_UNKNOWN_RESOURCE_PRIORITY = 417,
    PJSIP_SC_BAD_EXTENSION = 420,
    PJSIP_SC_EXTENSION_REQUIRED = 421,
    PJSIP_SC_SESSION_TIMER_TOO_SMALL = 422,
    PJSIP_SC_INTERVAL_TOO_BRIEF = 423,
    PJSIP_SC_BAD_LOCATION_INFORMATION = 424,
    PJSIP_SC_USE_IDENTITY_HEADER = 428,
    PJSIP_SC_PROVIDE_REFERRER_HEADER = 429,
    PJSIP_SC_FLOW_FAILED = 430,
    PJSIP_SC_ANONIMITY_DISALLOWED = 433,
    PJSIP_SC_BAD_IDENTITY_INFO = 436,
    PJSIP_SC_UNSUPPORTED_CERTIFICATE = 437,
    PJSIP_SC_INVALID_IDENTITY_HEADER = 438,
    PJSIP_SC_FIRST_HOP_LACKS_OUTBOUND_SUPPORT = 439,
    PJSIP_SC_MAX_BREADTH_EXCEEDED = 440,
    PJSIP_SC_BAD_INFO_PACKAGE = 469,
    PJSIP_SC_CONSENT_NEEDED = 470,
    PJSIP_SC_TEMPORARILY_UNAVAILABLE = 480,
    PJSIP_SC_CALL_TSX_DOES_NOT_EXIST = 481,
    PJSIP_SC_LOOP_DETECTED = 482,
    PJSIP_SC_TOO_MANY_HOPS = 483,
    PJSIP_SC_ADDRESS_INCOMPLETE = 484,
    PJSIP_AC_AMBIGUOUS = 485,
    PJSIP_SC_BUSY_HERE = 486,
    PJSIP_SC_REQUEST_TERMINATED = 487,
    PJSIP_SC_NOT_ACCEPTABLE_HERE = 488,
    PJSIP_SC_BAD_EVENT = 489,
    PJSIP_SC_REQUEST_UPDATED = 490,
    PJSIP_SC_REQUEST_PENDING = 491,
    PJSIP_SC_UNDECIPHERABLE = 493,
    PJSIP_SC_SECURITY_AGREEMENT_NEEDED = 494,

    PJSIP_SC_INTERNAL_SERVER_ERROR = 500,
    PJSIP_SC_NOT_IMPLEMENTED = 501,
    PJSIP_SC_BAD_GATEWAY = 502,
    PJSIP_SC_SERVICE_UNAVAILABLE = 503,
    PJSIP_SC_SERVER_TIMEOUT = 504,
    PJSIP_SC_VERSION_NOT_SUPPORTED = 505,
    PJSIP_SC_MESSAGE_TOO_LARGE = 513,
    PJSIP_SC_PUSH_NOTIFICATION_SERVICE_NOT_SUPPORTED = 555,
    PJSIP_SC_PRECONDITION_FAILURE = 580,

    PJSIP_SC_BUSY_EVERYWHERE = 600,
    PJSIP_SC_DECLINE = 603,
    PJSIP_SC_DOES_NOT_EXIST_ANYWHERE = 604,
    PJSIP_SC_NOT_ACCEPTABLE_ANYWHERE = 606,
    PJSIP_SC_UNWANTED = 607,
    PJSIP_SC_REJECTED = 608,

    PJSIP_SC_TSX_TIMEOUT = PJSIP_SC_REQUEST_TIMEOUT,
    /*PJSIP_SC_TSX_RESOLVE_ERROR = 702,*/
    PJSIP_SC_TSX_TRANSPORT_ERROR = PJSIP_SC_SERVICE_UNAVAILABLE,

    /* This is not an actual status code, but rather a constant
     * to force GCC to use 32bit to represent this enum, since
     * we have a code in PJSUA-LIB that assigns an integer
     * to this enum (see pjsua_acc_get_info() function).
     */
    PJSIP_SC__force_32bit = 0x7FFFFFFF

} pjsip_status_code;

3.7 Non-Standard Parameter Elements

  • 标准参数有特定字段“standard” parameters (e.g. URI parameters, header field parameters) will normally be represented as individual attributes/fields of the corresponding structure.
  • 非标准参数在other_param中Parameters that are not “standard” will be put in a list of parameters, with each parameter is represented as pjsip_param structure. Non standard parameter normally is declared as other_param field in the owning structure.
/**
 * Generic parameter, normally used in other_param or header_param.
 */
typedef struct pjsip_param
{
    PJ_DECL_LIST_MEMBER(struct pjsip_param);    /**< Generic list member.   */
    pj_str_t        name;                       /**< Param/header name.     */
    pj_str_t        value;                      /**< Param/header value.    */
} pjsip_param;

相关函数

/**
 * Find the specified parameter name in the list. The name will be compared
 * in case-insensitive comparison.
 *
 * @param param_list    List of parameters to find.
 * @param name          Parameter/header name to find.
 *
 * @return              The parameter if found, or NULL.
 */
PJ_DECL(pjsip_param*) pjsip_param_find( const pjsip_param *param_list,
                                        const pj_str_t *name );


/**
 * Alias for pjsip_param_find()
 */
PJ_INLINE(pjsip_param*) pjsip_param_cfind(const pjsip_param *param_list,
                                          const pj_str_t *name)
{
    return pjsip_param_find(param_list, name);
}

/**
 * Compare two parameter lists.
 *
 * @param param_list1   First parameter list.
 * @param param_list2   Second parameter list.
 * @param ig_nf         If set to 1, do not compare parameters that only
 *                      appear in one of the list.
 *
 * @return              Zero if the parameter list are equal, non-zero
 *                      otherwise.
 */
PJ_DECL(int) pjsip_param_cmp(const pjsip_param *param_list1,
                             const pjsip_param *param_list2,
                             pj_bool_t ig_nf);

/**
 * Duplicate the parameters.
 *
 * @param pool          Pool to allocate memory from.
 * @param dst_list      Destination list.
 * @param src_list      Source list.
 */
PJ_DECL(void) pjsip_param_clone(pj_pool_t *pool, pjsip_param *dst_list,
                                const pjsip_param *src_list);

/**
 * Duplicate the parameters.
 *
 * @param pool          Pool to allocate memory from.
 * @param dst_list      Destination list.
 * @param src_list      Source list.
 */
PJ_DECL(void) pjsip_param_shallow_clone(pj_pool_t *pool, 
                                        pjsip_param *dst_list,
                                        const pjsip_param *src_list);

/**
 * Print parameters.
 *
 * @param param_list    The parameter list.
 * @param buf           Buffer.
 * @param size          Size of buffer.
 * @param pname_unres   Specification of allowed characters in pname.
 * @param pvalue_unres  Specification of allowed characters in pvalue.
 * @param sep           Separator character (either ';', ',', or '?').
 *                      When separator is set to '?', this function will
 *                      automatically adjust the separator character to
 *                      '&' after the first parameter is printed.
 *
 * @return              The number of bytes printed, or -1 on errr.
 */
PJ_DECL(pj_ssize_t) pjsip_param_print_on(const pjsip_param *param_list,
                                         char *buf, pj_size_t size,
                                         const pj_cis_t *pname_unres,
                                         const pj_cis_t *pvalue_unres,
                                         int sep);

Chapter 4:Parser

4.1 Features

  • zero-copy for all message elements,only pointer and length
  • uses PJLIB’s memory pool
  • zero synchronization
  • uses PJLIB’s try/catch exception framework
  • top-down, handwritten parser
  • It’s reentrant
  • It’s extensible

4.2 Functions

4.2.1 Message Parsing

/**
 * Parse a packet buffer and build a full SIP message from the packet. This
 * function parses all parts of the message, including request/status line,
 * all headers, and the message body. The message body however is only 
 * treated as a text block, ie. the function will not try to parse the content
 * of the body.
 *
 * Note that the input string buffer MUST be NULL terminated and have
 * length at least size+1 (size MUST NOT include the NULL terminator).
 *
 * @param pool          The pool to allocate memory.
 * @param buf           The input buffer, which MUST be NULL terminated.
 * @param size          The length of the string (not counting NULL terminator).
 * @param err_list      If this parameter is not NULL, then the parser will
 *                      put error messages during parsing in this list.
 *
 * @return              The message or NULL when failed. No exception is thrown
 *                      by this function (or any public parser functions).
 */
PJ_DECL(pjsip_msg *) pjsip_parse_msg( pj_pool_t *pool, 
                                      char *buf, pj_size_t size,
                                      pjsip_parser_err_report *err_list);


/**
 * Parse a packet buffer and build a rdata. The resulting message will be
 * stored in \c msg field in the \c rdata. This behaves pretty much like
 * #pjsip_parse_msg(), except that it will also initialize the header fields
 * in the \c rdata.
 *
 * This function is normally called by the transport layer.
 *
 * Note that the input string buffer MUST be NULL terminated and have
 * length at least size+1 (size MUST NOT include the NULL terminator).
 *
 * @param buf           The input buffer, which MUST be NULL terminated.
 * @param size          The length of the string (not counting NULL terminator).
 * @param rdata         The receive data buffer to store the message and
 *                      its elements.
 *
 * @return              The message inside the rdata if successfull, or NULL.
 */
PJ_DECL(pjsip_msg *) pjsip_parse_rdata( char *buf, pj_size_t size,
                                        pjsip_rx_data *rdata );

/**
 * Check incoming packet to see if a (probably) valid SIP message has been 
 * received.
 * Note that the input string buffer MUST be NULL terminated and have
 * length at least size+1 (size MUST NOT include the NULL terminator).
 *
 * @param buf           The input buffer, which must be NULL terminated.
 * @param size          The length of the string (not counting NULL terminator).
 * @param is_datagram   Put non-zero if transport is datagram oriented.
 * @param msg_size      [out] If message is valid, this parameter will contain
 *                      the size of the SIP message (including body, if any).
 *
 * @return              PJ_SUCCESS if a message is found, or an error code.
 */
PJ_DECL(pj_status_t) pjsip_find_msg(const char *buf, 
                                    pj_size_t size, 
                                    pj_bool_t is_datagram, 
                                    pj_size_t *msg_size);

4.2.2 URI Parsing

/**
 * Parse an URI in the input and return the correct instance of URI.
 * Note that the input string buffer MUST be NULL terminated and have
 * length at least size+1 (size MUST NOT include the NULL terminator).
 *
 * @param pool          The pool to get memory allocations.
 * @param buf           The input buffer, which MUST be NULL terminated.
 * @param size          The length of the string (not counting NULL terminator).
 * @param options       If no options are given (value is zero), the object 
 *                      returned is dependent on the syntax of the URI, 
 *                      eg. basic SIP URL, TEL URL, or name address. 
 *                      If option PJSIP_PARSE_URI_AS_NAMEADDR is given,
 *                      then the returned object is always name address object,
 *                      with the relevant URI object contained in the name 
 *                      address object.
 * @return              The URI or NULL when failed. No exception is thrown by 
 *                      this function (or any public parser functions).
 */
PJ_DECL(pjsip_uri*) pjsip_parse_uri( pj_pool_t *pool, 
                                     char *buf, pj_size_t size,
                                     unsigned options);

4.2.3 Header Parsing

/**
 * Parse the content of a header and return the header instance.
 * This function parses the content of a header (ie. part after colon) according
 * to the expected name, and will return the correct instance of header.
 *
 * Note that the input string buffer MUST be NULL terminated and have
 * length at least size+1 (size MUST NOT include the NULL terminator). 
 *
 * @param pool          Pool to allocate memory for the header.
 * @param hname         Header name which is used to find the correct function
 *                      to parse the header.
 * @param line          Header content, which must be NULL terminated.
 * @param size          The length of the string (not counting NULL terminator,
 *                      if any).
 * @param parsed_len    If the value is not NULL, then upon return the function
 *                      will fill the pointer with the length of the string
 *                      that has been parsed. This is usefull for two purposes,
 *                      one is when the string may contain more than one header
 *                      lines, and two when an error happen the value can
 *                      pinpoint the location of the error in the buffer.
 *
 * @return              The instance of the header if parsing was successful,
 *                      or otherwise a NULL pointer will be returned.
 */
PJ_DECL(void*) pjsip_parse_hdr( pj_pool_t *pool, const pj_str_t *hname,
                                char *line, pj_size_t size,
                                int *parsed_len);
                                
/**
 * Parse header line(s). Multiple headers can be parsed by this function.
 * When there are multiple headers, the headers MUST be separated by either
 * a newline (as in SIP message) or ampersand mark (as in URI). This separator
 * is optional for the last header.
 *
 * Note that the input string buffer MUST be NULL terminated and have
 * length at least size+1 (size MUST NOT include the NULL terminator).
 *
 * @param pool          The pool.
 * @param input         The input text to parse, which must be NULL terminated.
 * @param size          The text length (not counting NULL terminator).
 * @param hlist         The header list to store the parsed headers.
 *                      This list must have been initialized before calling 
 *                      this function.
 * @param options       Specify 1 here to make parsing stop when error is
 *                      encountered when parsing the header. Otherwise the
 *                      error is silently ignored and parsing resumes to the
 *                      next line.
 * @return              zero if successfull, or -1 if error is encountered. 
 *                      Upon error, the \a hlist argument MAY contain 
 *                      successfully parsed headers.
 */
PJ_DECL(pj_status_t) pjsip_parse_headers( pj_pool_t *pool, char *input,
                                          pj_size_t size, pjsip_hdr *hlist,
                                          unsigned options);


pjsip_parse_headers是multiple

4.3 Extending Parser

Chapter 5:Message Buffers

5.1 Receive Data Buffer

5.1.1 Receive Data Buffer Structure

A SIP message received by PJSIP will be passed around to different PJSIP software components as pjsip_rx_data instead of a plain message. This structure contains all information describing the received message.各个部件彼此传递pjsip_rx_data,包含关于received message的信息

/**
 * Incoming message buffer.
 * This structure keep all the information regarding the received message. This
 * buffer lifetime is only very short, normally after the transaction has been
 * called, this buffer will be deleted/recycled. So care must be taken when
 * allocating storage from the pool of this buffer.
 */
struct pjsip_rx_data
{

    /**
     * tp_info is part of rdata that remains static for the duration of the
     * buffer. It is initialized when the buffer was created by transport.
     */
    struct 
    {
        /** Memory pool for this buffer. */
        pj_pool_t               *pool;

        /** The transport object which received this packet. */
        pjsip_transport         *transport;

        /** Other transport specific data to be attached to this buffer. */
        void                    *tp_data;

        /** Ioqueue key. */
        pjsip_rx_data_op_key     op_key;

    } tp_info;


    /**
     * pkt_info is initialized by transport when it receives an incoming
     * packet.
     */
    struct
    {
        /** Time when the message was received. */
        pj_time_val              timestamp;

        /** Pointer to the original packet. */
        char                     packet[PJSIP_MAX_PKT_LEN];

        /** Zero termination for the packet. */
        pj_uint32_t              zero;

        /** The length of the packet received. */
        pj_ssize_t               len;

        /** The source address from which the packet was received. */
        pj_sockaddr              src_addr;

        /** The length of the source address. */
        int                      src_addr_len;

        /** The IP source address string (NULL terminated). */
        char                     src_name[PJ_INET6_ADDRSTRLEN];

        /** The IP source port number. */
        int                      src_port;

    } pkt_info;


    /**
     * msg_info is initialized by transport mgr (tpmgr) before this buffer
     * is passed to endpoint.
     */
    struct
    {
        /** Start of msg buffer. */
        char                    *msg_buf;

        /** Length fo message. */
        int                      len;

        /** The parsed message, if any. */
        pjsip_msg               *msg;

        /** Short description about the message. 
         *  Application should use #pjsip_rx_data_get_info() instead.
         */
        char                    *info;

        /** The Call-ID header as found in the message. */
        pjsip_cid_hdr           *cid;

        /** The From header as found in the message. */
        pjsip_from_hdr          *from;

        /** The To header as found in the message. */
        pjsip_to_hdr            *to;

        /** The topmost Via header as found in the message. */
        pjsip_via_hdr           *via;

        /** The CSeq header as found in the message. */
        pjsip_cseq_hdr          *cseq;

        /** Max forwards header. */
        pjsip_max_fwd_hdr       *max_fwd;

        /** The first route header. */
        pjsip_route_hdr         *route;

        /** The first record-route header. */
        pjsip_rr_hdr            *record_route;

        /** Content-type header. */
        pjsip_ctype_hdr         *ctype;

        /** Content-length header. */
        pjsip_clen_hdr          *clen;

        /** "Require" header containing aggregates of all Require
         *  headers found in the message, or NULL. 
         */
        pjsip_require_hdr       *require;

        /** "Supported" header containing aggregates of all Supported
         *  headers found in the message, or NULL. 
         */
        pjsip_supported_hdr     *supported;

        /** The list of error generated by the parser when parsing 
            this message. 
         */
        pjsip_parser_err_report parse_err;

    } msg_info;


    /**
     * endpt_info is initialized by endpoint after this buffer reaches
     * endpoint.
     */
    struct
    {
        /** 
         * Data attached by modules to this message. 
         */
        void    *mod_data[PJSIP_MAX_MODULE];

    } endpt_info;

};

5.2 Transmit Data Buffer (pjsip_tx_data)

当要向往发送消息使用结构:pjsip_tx_data transmit data buffer 传输数据缓冲区提供了一个内存池,所有属于该消息的消息字段都必须从该内存池中分配,它还提供了一个引用计数器、锁保护和传输层处理该消息所需的其他信息。

c/**
 * Data structure for sending outgoing message. Application normally creates
 * this buffer by calling #pjsip_endpt_create_tdata.
 *
 * The lifetime of this buffer is controlled by the reference counter in this
 * structure, which is manipulated by calling #pjsip_tx_data_add_ref and
 * #pjsip_tx_data_dec_ref. When the reference counter has reached zero, then
 * this buffer will be destroyed.
 *
 * A transaction object normally will add reference counter to this buffe
 * when application calls #pjsip_tsx_send_msg, because it needs to keep the
 * message for retransmission. The transaction will release the reference
 * counter once its state has reached final state.
 */
struct pjsip_tx_data
{
    /** This is for transmission queue; it's managed by transports. */
    PJ_DECL_LIST_MEMBER(struct pjsip_tx_data);

    /** Memory pool for this buffer. */
    pj_pool_t           *pool;

    /** A name to identify this buffer. */
    char                 obj_name[PJ_MAX_OBJ_NAME];

    /** Short information describing this buffer and the message in it. 
     *  Application should use #pjsip_tx_data_get_info() instead of
     *  directly accessing this member.
     */
    char                *info;

    /** For response message, this contains the reference to timestamp when 
     *  the original request message was received. The value of this field
     *  is set when application creates response message to a request by
     *  calling #pjsip_endpt_create_response.
     */
    pj_time_val          rx_timestamp;

    /** The transport manager for this buffer. */
    pjsip_tpmgr         *mgr;

    /** Ioqueue asynchronous operation key. */
    pjsip_tx_data_op_key op_key;

    /** Lock object. */
    pj_lock_t           *lock;

    /** The message in this buffer. */
    pjsip_msg           *msg;

    /** Strict route header saved by #pjsip_process_route_set(), to be
     *  restored by #pjsip_restore_strict_route_set().
     */
    pjsip_route_hdr     *saved_strict_route;

    /** Buffer to the printed text representation of the message. When the
     *  content of this buffer is set, then the transport will send the content
     *  of this buffer instead of re-printing the message structure. If the
     *  message structure has changed, then application must invalidate this
     *  buffer by calling #pjsip_tx_data_invalidate_msg.
     */
    pjsip_buffer         buf;

    /** Reference counter. */
    pj_atomic_t         *ref_cnt;

    /** Being processed by transport? */
    int                  is_pending;

    /** Transport manager internal. */
    void                *token;

    /** Callback to be called when this tx_data has been transmitted.   */
    void               (*cb)(void*, pjsip_tx_data*, pj_ssize_t);

    /** Destination information, to be used to determine the network address
     *  of the message. For a request, this information is  initialized when
     *  the request is sent with #pjsip_endpt_send_request_stateless() and
     *  network address is resolved. For CANCEL request, this information
     *  will be copied from the original INVITE to make sure that the CANCEL
     *  request goes to the same physical network address as the INVITE
     *  request.
     */
    struct
    {
        /** Server name. 
         */
        pj_str_t                 name;

        /** Server addresses resolved. 
         */
        pjsip_server_addresses   addr;

        /** Current server address being tried. 
         */
        unsigned cur_addr;

    } dest_info;

    /** Transport information, only valid during on_tx_request() and 
     *  on_tx_response() callback.
     */
    struct
    {
        pjsip_transport     *transport;     /**< Transport being used.  */
        pj_sockaddr          dst_addr;      /**< Destination address.   */
        int                  dst_addr_len;  /**< Length of address.     */
        char                 dst_name[PJ_INET6_ADDRSTRLEN]; /**< Destination address.   */
        int                  dst_port;      /**< Destination port.      */
    } tp_info;

    /** 
     * Transport selector, to specify which transport to be used. 
     * The value here must be set with pjsip_tx_data_set_transport(),
     * to allow reference counter to be set properly.
     */
    pjsip_tpselector        tp_sel;

    /**
     * Special flag to indicate that this transmit data is a request that has
     * been updated with proper authentication response and is ready to be
     * sent for retry.
     */
    pj_bool_t               auth_retry;

    /**
     * Arbitrary data attached by PJSIP modules.
     */
    void                    *mod_data[PJSIP_MAX_MODULE];

    /**
     * If via_addr is set, it will be used as the "sent-by" field of the
     * Via header for outgoing requests as long as the request uses via_tp
     * transport. Normally application should not use or access these fields.
     */
    pjsip_host_port          via_addr;      /**< Via address.           */
    const void              *via_tp;        /**< Via transport.         */
};

Chapter 6:Transport Layer

6.1 Transport Layer Design

send/receive messages across the network

6.1.1 “Class Diagram”

endpoint 中有一个transport manager的实例pjsip_tpmgr *transport_mgr;transport manager管理transport和transport factory

6.1.2 Transport Manager

  • 管理transports的生命周期通过transport’s 引用计数器 and 空闲时间

  • Manages transport factories.

  • 接受来自transport的包,解析包,把sip消息分发给endpoint

  • 找到匹配的transport发送SIP message给特定的目的地,通过transport type and remote address.

    Transport manager is normally not visible to applications; applications should use the functions provided by

    endpoint.

6.1.3 Transport Factory

传输工厂(pjsip_tpfactory)用于创建到remote endpoint的动态连接。示例:TCP传输,其中需要为每个目的地创建一个TCP传输。当transport manager检测到它需要创建到新目的地的新transport时,它会找到具有匹配规范(即传输类型)的transport factory,并要求factory创建连接。

6.1.4 Transport

  • 对应数据结构 pjsip_transport

    /**
     * This structure represent the "public" interface of a SIP transport.
     * Applications normally extend this structure to include transport
     * specific members.
     */
    struct pjsip_transport
    {
        char                    obj_name[PJ_MAX_OBJ_NAME];  /**< Name. */
    
        pj_pool_t              *pool;           /**< Pool used by transport.    */
        pj_atomic_t            *ref_cnt;        /**< Reference counter.         */
        pj_lock_t              *lock;           /**< Lock object.               */
        pj_grp_lock_t          *grp_lock;       /**< Group lock for sync with
                                                     ioqueue and timer.         */
        pj_bool_t               tracing;        /**< Tracing enabled?           */
        pj_bool_t               is_shutdown;    /**< Being shutdown?            */
        pj_bool_t               is_destroying;  /**< Destroy in progress?       */
    
        /** Key for indexing this transport in hash table. */
        pjsip_transport_key     key;
    
        char                   *type_name;      /**< Type name.                 */
        unsigned                flag;           /**< #pjsip_transport_flags_e   */
        char                   *info;           /**< Transport info/description.*/
    
        int                     addr_len;       /**< Length of addresses.       */
        pj_sockaddr             local_addr;     /**< Bound address.             */
        pjsip_host_port         local_name;     /**< Published name (eg. STUN). */
        pjsip_host_port         remote_name;    /**< Remote address name.       */
        pjsip_transport_dir     dir;            /**< Connection direction.      */
        
        pjsip_endpoint         *endpt;          /**< Endpoint instance.         */
        pjsip_tpmgr            *tpmgr;          /**< Transport manager.         */
        pjsip_tpfactory        *factory;        /**< Factory instance. Note: it
                                                     may be invalid/shutdown.   */
        pj_timer_entry          idle_timer;     /**< Timer when ref cnt is zero.*/
    
        pj_timestamp            last_recv_ts;   /**< Last time receiving data.  */
        pj_size_t               last_recv_len;  /**< Last received data length. */
    
        void                   *data;           /**< Internal transport data.   */
        unsigned                initial_timeout;/**< Initial timeout interval
                                                     to be applied to incoming
                                                     TCP/TLS transports when no
                                                     valid data received after
                                                     a successful connection.   */
    
        /**
         * Function to be called by transport manager to send SIP message.
         *
         * @param transport     The transport to send the message.
         * @param packet        The buffer to send.
         * @param length        The length of the buffer to send.
         * @param op_key        Completion token, which will be supplied to
         *                      caller when pending send operation completes.
         * @param rem_addr      The remote destination address.
         * @param addr_len      Size of remote address.
         * @param callback      If supplied, the callback will be called
         *                      once a pending transmission has completed. If
         *                      the function completes immediately (i.e. return
         *                      code is not PJ_EPENDING), the callback will not
         *                      be called.
         *
         * @return              Should return PJ_SUCCESS only if data has been
         *                      succesfully queued to operating system for 
         *                      transmission. Otherwise it may return PJ_EPENDING
         *                      if the underlying transport can not send the
         *                      data immediately and will send it later, which in
         *                      this case caller doesn't have to do anything 
         *                      except wait the calback to be called, if it 
         *                      supplies one.
         *                      Other return values indicate the error code.
         */
        pj_status_t (*send_msg)(pjsip_transport *transport, 
                                pjsip_tx_data *tdata,
                                const pj_sockaddr_t *rem_addr,
                                int addr_len,
                                void *token,
                                pjsip_transport_callback callback);
    
        /**
         * Instruct the transport to initiate graceful shutdown procedure.
         * After all objects release their reference to this transport,
         * the transport will be deleted.
         *
         * Note that application MUST use #pjsip_transport_shutdown() instead.
         *
         * @param transport     The transport.
         *
         * @return              PJ_SUCCESS on success.
         */
        pj_status_t (*do_shutdown)(pjsip_transport *transport);
    
        /**
         * Forcefully destroy this transport regardless whether there are
         * objects that currently use this transport. This function should only
         * be called by transport manager or other internal objects (such as the
         * transport itself) who know what they're doing. Application should use
         * #pjsip_transport_shutdown() instead.
         *
         * @param transport     The transport.
         *
         * @return              PJ_SUCCESS on success.
         */
        pj_status_t (*destroy)(pjsip_transport *transport);
    
        /*
         * Application may extend this structure..
         */
    };
    
    
  • 每一个实例代表一个socket handle

  • 对于接收,每个transport对象必须必须自己从网络上接受packet,并且发送其给transport manager ,他自己本身不能提供poll操作。比较好的办法是将 transport’s socket handle注册到 endpoint’s I/O queue (pj_ioqueue_t)。当endpoint poll I/O queue 时从网上来的包就会被,transport接收。接受后,必须调用pjsip_tpmgr_receive_packet(),解析并传递该信息 。 The transport object must initialize both tp_info and pkt_info member of receive data buffer (pjsip_rx_data).

  • 对于发送,每个传输对象都有一个指向将消息发送到网络的函数的指针(即传输对象的send_msg()属性)。应用程序(或堆栈)通过调用pjsip_transport_send()函数将消息发送到网络,该函数最终将到达传输对象,并且将调用send_msg()。数据包的发送可以异步完成;如果是这样,传输必须在send_msg()中返回PJ_EPENDING状态,并在消息发送到目的地时调用参数中指定的回调。

  • 对于transport的管理,通过pjsip_transport_register()将transport注册到transport manager,在此之前transport 实例必须完成初始化。传输的生命周期由transport manager自动管理。每次transport的参考计数器达到零时,将启动一个空闲计时器。当空闲计时器到期且引用计数器仍然为零时,transport manager将通过调用pjsip_transport_unregister()来销毁传输。此函数将传输从transport manager的哈希表中注销,并最终销毁传输。

  • 对于错误处理:由transport user处理

6.2 Using Transports

6.2.1 Function Reference

PJ_DEF(pj_status_t) pjsip_endpt_acquire_transport(pjsip_endpoint *endpt,
                                                  pjsip_transport_type_e type,
                                                  const pj_sockaddr_t *remote,
                                                  int addr_len,
                                                  const pjsip_tpselector *sel,
                                                  pjsip_transport **transport)

获取类型为t_type的transport,用于发送消息到目的地remote_addr。请注意,如果成功获取transport,则transport的引用计数器将增加。通过endpoint 中的transport manager找transport

/**
 * Add reference counter to the specified transport. Any objects that wishes
 * to keep the reference of the transport MUST increment the transport's
 * reference counter to prevent it from being destroyed.
 *
 * @param tp            The transport instance.
 *
 * @return              PJ_SUCCESS on success or the appropriate error code.
 */
PJ_DECL(pj_status_t) pjsip_transport_add_ref( pjsip_transport *tp );

/**
 * Decrement reference counter of the specified transport. When an object no
 * longer want to keep the reference to the transport, it must decrement the
 * reference counter. When the reference counter of the transport reaches 
 * zero, the transport manager will start the idle timer to destroy the
 * transport if no objects acquire the reference counter during the idle
 * interval.
 *
 * @param tp            The transport instance.
 *
 * @return              PJ_SUCCESS on success.
 */
PJ_DECL(pj_status_t) pjsip_transport_dec_ref( pjsip_transport *tp );
/**
 * This is a low-level function to send a SIP message using the specified
 * transport to the specified destination.
 * 
 * @param tr        The SIP transport to be used.
 * @param tdata     Transmit data buffer containing SIP message.
 * @param addr      Destination address.
 * @param addr_len  Length of destination address.
 * @param token     Arbitrary token to be returned back to callback.
 * @param cb        Optional callback to be called to notify caller about
 *                  the completion status of the pending send operation.
 *
 * @return          If the message has been sent successfully, this function
 *                  will return PJ_SUCCESS and the callback will not be 
 *                  called. If message cannot be sent immediately, this
 *                  function will return PJ_EPENDING, and application will
 *                  be notified later about the completion via the callback.
 *                  Any statuses other than PJ_SUCCESS or PJ_EPENDING
 *                  indicates immediate failure, and in this case the 
 *                  callback will not be called.
 */
PJ_DECL(pj_status_t) pjsip_transport_send( pjsip_transport *tr, 
                                           pjsip_tx_data *tdata,
                                           const pj_sockaddr_t *addr,
                                           int addr_len,
                                           void *token,
                                           pjsip_tp_send_callback cb);

使用transport将tdata中的消息发送到remote_addr。如果函数立即完成并且数据已经发送,则函数返回PJ_SUCCESS。如果函数立即以错误结束,则返回一个非零错误码。在这两种情况下,回调都不会被调用。如果函数不能立即完成(例如,当底层套接字缓冲区已满时),该函数将返回PJ_EPENDING,并通过回调cb通知调用方完成。如果挂起的发送操作以错误完成,则在回调的bytes_sent参数中,错误代码将以错误代码的负值表示(要获取错误代码,使用“pj_status_t status = -bytes_sent”)。这个函数按原样发送消息;它不会对消息执行任何验证。Via头也不会被这个函数修改。

6.3 Extending Transports

6.4 Initializing Transports

PJSIP doesn’t start any transports by default. it is the responsibility of the application to initialize and start any transports that it wishes to use.

6.4.1 UDP Transport Initialization

/**
 * Start UDP transport.
 *
 * @param endpt         The SIP endpoint.
 * @param local         Optional local address to bind. If this argument
 *                      is NULL, the UDP transport will be bound to arbitrary
 *                      UDP port.
 * @param a_name        Published address (only the host and port portion is 
 *                      used). If this argument is NULL, then the bound address
 *                      will be used as the published address.
 * @param async_cnt     Number of simultaneous async operations.
 * @param p_transport   Pointer to receive the transport.
 *
 * @return              PJ_SUCCESS when the transport has been successfully
 *                      started and registered to transport manager, or
 *                      the appropriate error code.
 */
PJ_DECL(pj_status_t) pjsip_udp_transport_start(pjsip_endpoint *endpt,
                                               const pj_sockaddr_in *local,
                                               const pjsip_host_port *a_name,
                                               unsigned async_cnt,
                                               pjsip_transport **p_transport);

创建、初始化、注册和启动一个新的UDP传输。UDP套接字将绑定到local_addr。如果端点位于防火墙/NAT或其他端口转发设备之后,则可以使用pub_addr作为此传输的广告地址;否则,pub_addr应该与local_addr相同。参数async_cnt指定此传输允许多少个同时操作,以及最大值性能,该值应该等于节点中的处理器数量。

如果传输成功启动,该函数返回PJ_SUCCESS,如果应用程序想要立即使用传输,则在p_transport参数中返回传输。申请不需要向运输经理登记运输;当函数成功返回时,此函数已经完成了该操作。当出现错误时,该函数返回一个非零错误代码。

/**
 * Attach IPv4 UDP socket as a new transport and start the transport.
 *
 * @param endpt         The SIP endpoint.
 * @param sock          UDP socket to use.
 * @param a_name        Published address (only the host and port portion is 
 *                      used).
 * @param async_cnt     Number of simultaneous async operations.
 * @param p_transport   Pointer to receive the transport.
 *
 * @return              PJ_SUCCESS when the transport has been successfully
 *                      started and registered to transport manager, or
 *                      the appropriate error code.
 */
PJ_DECL(pj_status_t) pjsip_udp_transport_attach(pjsip_endpoint *endpt,
                                                pj_sock_t sock,
                                                const pjsip_host_port *a_name,
                                                unsigned async_cnt,
                                                pjsip_transport **p_transport);

UDP socket is already available

Chapter 7:Sending Messages

有状态发送和无状态发送:无状态是指协议对于事务处理没有记忆能力.缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大.另一方面,在服务器不需要先前信息时它的应答就较快.

7.1 Sending Messages Overview

Receiving incoming message is handled in on_rx_request() and on_rx_response() callback of each module

This chapter will describe about the basic way to send outgoing messages本章介绍发送outgoing messages

7.1.1 Creating Messages

response messages:pjsip_endpt_create_response()

request messages: pjsip_endpt_create_request(),pjsip_endpt_create_request_from_hdr(), pjsip_endpt_create_ack(), orpjsip_endpt_create_cancel()

proxies 利用incoming messages创建 request or response messages:pjsip_endpt_create_request_fwd(),pjsip_endpt_create_response_fwd()

手动创建 request or response messages:使用pjsip_endpt_create_tdata()创建传输缓冲区,使用pjsip_msg_create()创建消息,使用pjsip_msg_add_hdr()或pjsip_msg_insert_first_hdr()向消息添加报头字段,设置消息主体等。

7.1.2 Sending Messages

basic way:**pjsip_endpt_acquire_transport()**and **pjsip_transport_send()**需要知道目的地址,太底层

pjsip_endpt_send_request_stateless()

  • 根据Request-URI和Route headers中的参数确定要联系的目的地;
  • 使用RFC 3263(定位SIP服务器)中的程序解析目标服务器;
  • 选择并建立用于连接服务器的transport;
  • 修改发送通过在Via头反映当前正在使用的transport;
  • 使用当前transport发送消息;
  • 故障转移到下一个服务器/transport,如果服务器不能使用当前transport

pjsip_endpt_send_response()

  • Follow the procedures in Section 18.2.2 of RFC 3261 to select which transport to use and which address to send response to,
  • Additionally conform to RFC 3581 about rport parameter,
  • Send the response using the selected transport,
  • Fail-over to next address when response failed to be sent using the selected transport, resolving the server according to RFC 3263 when necessary.

7.2 Function Reference

7.2.1 Sending Response

pjsip_endpt_create_response:根据状态码和状态信息和接受到的rdata,创建response message

pjsip_get_response_addr:根据rdata确定send 地址

pjsip_endpt_send_response: send response 无状态,需要目的地址

pjsip_endpt_respond_stateless 创建发送response一体

7.2.2 Sending Request

pjsip_endpt_create_tdata :Create a new, blank transmit data.

pjsip_endpt_create_request :创建request,Create a new request message of the specified method for the specified target URI, from, to, and contact. The call_id and cseq are optional. If text is specified, then a “text/plain” body is added. The request message has initial reference counter set to 1, and is then returned to sender in p_tdata.

pjsip_endpt_create_request_from_hdr:创建新的 request header by shallow-cloning the headers from the specified arguments.

pjsip_endpt_create_ack:创建ACK request 通过在tdata里的原有 request in tdata

pjsip_endpt_create_cancel

pjsip_endpt_send_request_stateless:Send request in tdata statelessly.

7.2.3 Stateless Proxy Forwarding

pjsip_endpt_create_request_fwd

pjsip_endpt_create_response_fwd

pjsip_calculate_branch_id

Chapter 8:Transactions

8.1 Design

8.1.1 Introduction

  • 通过pjsip_tsx_endpt_create_uac()** / pjsip_tsx_create_uas().创建Transaction
  • 初始化 UAS transaction后, application 调用 **pjsip_tsx_recv_msg()**接收初始request message,transaction state从NULL 变成 TRYING,transaction接收重发送信息
  • Subsequent request retransmissions will be absorbed by the transaction.
  • 当 application 想通过transaction发送request or response 可以调用 pjsip_tsx_send_msg().
  • Transaction state 自动转化当messages传递进来, transaction user 通过 on_tsx_state() 回调函数,获得通知
  • Transaction 自动销毁当状态达到 PJSIP_TSX_STATE_TERMINATED. Application可以调用pjsip_tsx_terminate().主动销毁

8.1.2 Timers and Retransmissions

有两种类型的timer,retransmission timer and timeout timer 这两种定时器的值由事务根据事务类型(UAS或UAC)、transport方式(可靠或不可靠)和方法(INVITE或非INVITE)自动设置。应用程序只能在全局基础上更改计时器的间隔值(甚至可能只在编译期间)。transaction处理 incoming and outgoing retransmissions。 Incoming retransmissions 被静默地吸收并被事务忽略;没有关于事务发出的传入重传的通知。如有需要,外发讯息会由交易自动重传;同样,事务在传出重传时也不会发出通知。

8.1.3 INVITE Final Response and ACK Request

Failed INVITE Request

Client transaction:当客户端INVITE transaction收到300-699对INVITE的最终响应时,它将自动发出对该响应的ACK请求。然后,事务在终止之前等待定时器D间隔,在此期间,任何传入的300-699响应重传将自动以ACK请求应答

Server transaction:当服务器INVITE事务被要求发送300-699最终响应时,它将发送该响应并保持重发该响应,直到接收到ACK请求或定时器H间隔已经过去。在此间隔期间,当收到ACK请求时,transaction将进入Confirmed状态,并在计时器I间隔结束后销毁。当定时器H超时而没有收到有效的ACK请求时,事务将被销毁。

Successfull INVITE Request

Client transaction:当客户端INVITE事务收到2xx对INVITE的最终响应时,它将在将响应传递给其事务用户(can be a dialog or application)后自动销毁自己。随后传入的2xx响应重传将直接传递到dialog or application。在任何情况下,应用程序必须在收到2xx对INVITE的最终响应后手动发送ACK请求。(传递销毁,手动ack)

Server transaction:当服务器INVITE事务被要求发送2xx最终响应时,它将发送该响应并保持重传该响应,直到收到ACK或事务被应用程序用pjsip_tsx_terminate()终止。(保持直到)为了简化实现,典型的UASdialog通常会让事务处理2xx INVITE响应的重传。但是代理应用程序必须在收到并发送2xx响应后立即销毁UAS事务,以便端到端用户代理处理2xx重传。

8.1.4 Incoming ACK Request

不成功transaction吸收ack用户收不到,成功transaction通知用户第一个ack

当服务器INVITE transaction以不成功的最终响应完成时,ACK请求将被transaction吸收;transaction用户将不会被通知传入的ACK请求。当服务器INVITE事务以2xx最终响应完成时,第一个ACK请求将被通知给事务用户。随后收到的ACK重发将不会通知交易用户。

8.1.5 Server Resolution and Transports

Transaction使用核心API pjsip_endpt_send_request_stateless()和pjsip_endpt_send_response()发送传出消息。这些函数提供服务器解析和传输建立以发送消息,并在检测到故障时故障转移到备用传输。事务使用这些函数提供的回调来监视传输的进度并跟踪正在使用的传输。

8.1.6 Via Header

Branch Parameter:UAC事务在Via标头中不存在唯一的分支参数时自动生成该参数。如果分支参数已经存在,则事务将使用它作为其键,遵守RFC 3261和RFC 2543设置的规则。

Via Sent-By:Via Sent-By始终由pjsip_endpt_send_request_stateless()和pjsip_endpt_send_response()放置。

8.2 Reference

pjsip_tsx_layer_init_module初始化事务层模块并将其注册到指定endpoint

pjsip_tsx_layer_instance Get the instance of transaction layer module.

pjsip_tsx_layer_destroy 销毁并且unregister endpoint

pjsip_tsx_create_uas 为传入的incoming request rdata 创建一个新的UAS transaction,事务用户设置为tsx_user事务被自动初始化并注册到endpoint的事务表。

pjsip_tsx_create_uac 为传出的outgoing request tdata 创建一个新的UAC transaction,事务用户设置为tsx_user。事务将自动初始化并注册到事务表。请注意,在调用此函数之后,应用程序通常会调用pjsip_tsx_send_msg()来实际发送请求。

pjsip_tsx_recv_msg 应用程序必须在UAS事务创建后调用此函数,传递初始请求消息,以便事务状态可以从NULL移动到TRYING。调用事务用户的on_tsx_state()。

pjsip_tsx_send_msg 通过transaction发送消息。如果tdata为NULL,则将重传最后一条消息或在创建过程中指定的消息。当函数返回PJ_SUCCESS时,tdata引用计数器将递减。

pjsip_tsx_create_key

pjsip_tsx_layer_find_tsx

pjsip_tsx_terminate 强制终止具有指定状态代码st_code的事务tsx。通常应用程序不需要调用此函数,因为事务将根据其状态机终止并销毁自己。例如,当发送/接收到对INVITE的200/OK响应并且UA层想要手动处理200/OK响应的重传时,使用该功能。事务将发出事务状态更改事件(状态更改为PJSIP_TSX_STATE_STATED),然后它将被注销并立即被此函数销毁。

pjsip_rdata_get_tsx 获取传入消息中的transaction对象。

pjsip_endpt_respond 有状态 Send 集合体respond by creating a new UAS transaction for the incoming request.

pjsip_endpt_send_request Send the request by using an UAC transaction, and optionally request callback to be called when the transaction completes.

8.3 Sending Statefull Responses

区别7.3的stateless

on_rx_request中,表示先接受再发送。先创建uas (pjsip_tsx_create_uas),接受初始信息(pjsip_tsx_recv_msg),send(pjsip_tsx_send_msg)或者 直接pjsip_endpt_respond

8.4 Sending Statefull Request

创建request( pjsip_endpt_create_request) 创建uac(pjsip_tsx_create_uac),send(pjsip_tsx_send_msg)或者直接pjsip_endpt_send_request()

Chapter 10:Basic User Agent Layer (UA)

10.1 Basic Dialog Concept

Basic UA Dialog 提供用于管理SIP Dialog和dialog usages的基本设施,诸如基本dialog状态、会话计数器、呼叫ID、From、To和Contact报头、事务中的CSeq的排序以及路由集。

dialog PJSIPdialog不知道其session的状态。它不知道INVITE session是已建立还是已断开。事实上,dialog甚至不知道dialog中有什么类型的session。它只关心dialog中有多少活动session。dialog从一个活动session开始,当session计数器达到零并且最后一个transaction终止时,dialog将被销毁。可以在单个dialog中同时建立多个和不同类型的session。

10.1.1 Dialog Sessions

PJSIP Dialog框架中的Dialog session仅用引用计数器表示。每当dialog使用模块在该特定dialog中创建/销毁session时,该引用计数器都会递增和递减。

Dialog’s sessions 被dialog usages 创建

10.1.2 Dialog Usages

Dialog Usages是PJSIP modules注册在dialog,以接受dialog 事件。多个modules可以注册到一个dialog,因此dialog可以有多种用途。每个dialog Usage module负责处理特定的session。例如,subscribe usage module将在每次接收到新的SUBSCRIBE请求时创建新的subscribe session(增量dialog session计数器)

Dialog处理Dialog Usages和endpoint类似,在每个on_req_request()和on_req_response()事件上,dialog将事件传递给从较高优先级模块开始的每个Dialog Usage(即优先级较低的模块),直到其中一个模块返回true(即,非零),在这种情况下,dialog将停止事件的分发。on_tsx_state()通知将分发给所有dialog使用。每个dialog的使用都应该过滤掉不属于它的事务事件。

在其最基本的(即低级)使用中,应用程序直接管理dialog,并且它是dialog唯一“使用”(或用户)。在这种情况下,应用程序负责管理dialog中的session,这意味着处理所有请求和响应,并手动建立/拆除session。

10.1.3 Dialog Set

每个Dialog都是一个Dialog集的成员。Dialog集common local tag(即From标签)标识。通常,一个Dialog集只有一个Dialog作为成员。只有当发出INVITE分叉时,dialog集才会包含多个dialog,在这种情况下,接收到的每个带有不同To标记的响应消息将在同一dialog集中创建一个新dialog。在PJSIP中,dialog集被定义为不透明类型(即void*)。dialog结构(pjsip_dialog)有一个名为dlg_set的成员,用于标识它所属的dialog集。应用程序可以使用链接列表API来检索dialog的同级dialog(在同一dialog集中)。

10.1.4 Client Authentication

dialog维护一个客户端身份验证会话(pjsip_auth_clt_sess),用于验证dialog中所有下游服务器的请求。基本dialog使用适当的身份验证头(如果可用)验证每个传出请求。然而,认证挑战必须由dialog使用来处理;例如,基本dialog不会在401/407响应失败时自动重试请求。

10.1.5 Class Diagram

img

该图显示了dialog和它的使用之间的关系。在最基本/低级的场景中,应用程序模块是dialog的唯一用途。在更高级的场景中,一些高级模块(例如pjsip_invite_usage和pjsip_subscribe_usage)可以注册到dialog作为dialog usages,并且应用程序将从这些usages而不是直接从dialog接收事件。

该图还显示了PJSIP user_agent模块(pjsip_user_agent)。user_agent模块是所有dialog的“所有者”;user_agent模块维护当前活动的所有session集的哈希表。

10.1.6 Forking

先不看了

10.1.7 CSeq Sequencing

Dialog的本地cseq在发送请求时更新(与创建请求时相反)。当CSeq报头存在于请求中时,该值可以随着在dialog内发送请求而被更新。

当收到请求时,dialog的远程cseq将被更新。当dialog的远程cseq为空时,收到的第一个请求将设置dialog的远程cseq。对于后续的请求,当dialog接收到cseq低于dialog记录的cseq的请求时,此请求将由dialog自动无状态地回答,并返回500响应(内部服务器错误)。当请求的cseq大于dialog记录的cseq时,dialog将自动更新远程的cseq(包括当请求的cseq大于1时)。

10.1.8 Transactions

Dialog 总是有状态地运行。当传入请求到达时,它自动创建UAS transaction,当请求发送传出请求时,它创建UAC transaction。dialog无状态操作的唯一时间是当它接收到CSeq低于当前CSeq的传入请求时,在这种情况下,它将使用500(内部服务器错误)响应来回答请求。

当transaction代表dialog时(通过dialogAPI,对于UAS和UAC事务),transaction的transaction user(TU)被设置为user agent实例,并且dialog实例将被放置transaction的mod_data中以适当的index形式。index是user agent的模块ID。当事件或消息到达时,transaction将事件报告给user agent模块,user agent模块将查找dialog并将事件传递给dialog。

10.2 Basic UA API Reference

10.2.1 User Agent Module API

typedef pjsip_module pjsip_user_agent;User agent type.

pjsip_ua_init_module、pjsip_ua_instance : Get the instance of the user agent

pjsip_ua_destroy: Destroy the user agent module.

10.2.2 Dialog Structure

/**
 * This structure is used to describe dialog's participants, which in this
 * case is local party (i.e. us) and remote party.
 */
typedef struct pjsip_dlg_party
{
    pjsip_fromto_hdr    *info;      /**< From/To header, inc tag.       */
    pj_str_t             info_str;  /**< String rep of info header.     */
    pj_uint32_t          tag_hval;  /**< Hashed value of the tag.       */
    pjsip_contact_hdr   *contact;   /**< Contact header.                */
    pj_int32_t           first_cseq;/**< First CSeq seen.               */
    pj_int32_t           cseq;      /**< Next sequence number.          */
} pjsip_dlg_party;
/**
 * This structure describes the dialog structure. Application MUST NOT
 * try to SET the values here directly, but instead it MUST use the
 * appropriate dialog API. The dialog declaration only needs to be made 
 * visible because other PJSIP modules need to see it (e.g. INVITE session,
 * the event framework, etc.).
 *
 * Application MAY READ the dialog contents directly after it acquires
 * dialog lock.
 *
 * To acquire dialog lock, use #pjsip_dlg_inc_lock(), and to release it,
 * use #pjsip_dlg_dec_lock(). DO NOT USE pj_mutex_lock()/pj_mutex_unlock()
 * on the dialog's mutex directly, because this will not protect against
 * dialog being destroyed.
 */
struct pjsip_dialog
{
    /** The dialog set list. */
    PJ_DECL_LIST_MEMBER(pjsip_dialog);

    /* Dialog's system properties. */
    char                obj_name[PJ_MAX_OBJ_NAME];  /**< Standard id.       */
    pj_pool_t          *pool;       /**< Dialog's pool.                     */
    pjsip_user_agent   *ua;         /**< User agent instance.               */
    pjsip_endpoint     *endpt;      /**< Endpoint instance.                 */
    pj_grp_lock_t      *grp_lock_;  /**< Dialog's grp lock. Do not call!!
                                         Use pjsip_dlg_inc_lock() instead!  */

    /** The dialog set which this dialog belongs (opaque type). */
    void               *dlg_set;

    /* Dialog's session properties. */
    pjsip_dialog_state  state;      /**< Dialog state.                      */
    pjsip_uri          *target;     /**< Current target.                    */
    pjsip_target_set    target_set; /**< Target set, for UAC only.          */
    pjsip_hdr           inv_hdr;    /**< Headers from hparam in dest URL    */
    pjsip_dlg_party     local;      /**< Local party info.                  */
    pjsip_dlg_party     remote;     /**< Remote party info.                 */
    pjsip_hdr           rem_cap_hdr;/**< List of remote capability header.  */
    pjsip_role_e        role;       /**< Initial role.                      */
    pj_bool_t           uac_has_2xx;/**< UAC has received 2xx response?     */
    pj_bool_t           secure;     /**< Use secure transport?              */
    pj_bool_t           add_allow;  /**< Add Allow header in requests?      */
    pj_bool_t           ack_sent;   /**< ACK has been sent?                 */
    pjsip_cid_hdr      *call_id;    /**< Call-ID header.                    */
    pjsip_route_hdr     route_set;  /**< Route set.                         */
    pj_bool_t           route_set_frozen; /**< Route set has been set.      */
    pjsip_auth_clt_sess auth_sess;  /**< Client authentication session.     */
    pj_str_t            initial_dest;/**< Initial destination host (used for
                                          verifying remote TLS cert).       */

    /** Session counter. */
    int                 sess_count; /**< Number of sessions.                */

    /** Transaction counter. */
    int                 tsx_count;  /**< Number of pending transactions.    */

    /** Transport selector. */
    pjsip_tpselector    tp_sel;

    /* Dialog usages. */
    unsigned            usage_cnt;  /**< Number of registered usages.       */
    pjsip_module       *usage[PJSIP_MAX_MODULE]; /**< Array of usages, 
                                         priority sorted                    */

    /** Module specific data. */
    void               *mod_data[PJSIP_MAX_MODULE]; /**< Module data.       */

    /**
     * If via_addr is set, it will be used as the "sent-by" field of the
     * Via header for outgoing requests as long as the request uses via_tp
     * transport. Normally application should not use or access these fields.
     */
    pjsip_host_port     via_addr;   /**< Via address.                       */
    const void         *via_tp;     /**< Via transport.                     */
};

10.2.3 Dialog Creation API

pjsip_dlg_create_uac: 创建一个新的dialog并返回p_dlg参数中的实例。创建dialog后,应用程序可以通过调用pjsip_dlg_add_usage()将模块添加为dialog usages。

pjsip_dlg_create_uas:从传入请求(如INVITE、REFER或SUBSCRIBE)中找到的信息初始化UAS dialog,并将本地Contact设置为contact。如果未指定contact,则从请求的To标头中的URI初始化本地联系人。如果请求有To tag参数,dialog的本地tag将从该值初始化。否则,将调用全局唯一ID生成器来创建dialog的本地tag。

10.2.4 Dialog Termination

一旦会话计数器达到零并且所有挂起的事务都已终止,dialog通常会自动销毁。

pjsip_dlg_terminate()函数用于提前销毁dialog。此函数通常由dialog调用。应用程序应使用适当的更高级别会话API,如pjsip_inv_terminate(),它将销毁session和dialog。

销毁dialog并从UA模块的哈希表中取消注册。

10.2.5 Dialog Session Management API

以下函数用于管理dialog的会话计数器。 pjsip_dlg_inc_session、pjsip_dlg_dec_session一旦会话计数器达到零并且没有挂起的事务,dialog将被销毁。请注意,如果调用此函数时没有挂起的事务,则此函数可能会立即销毁dialog。

10.2.6 Dialog Usages API

manage dialog usages in a dialog

pjsip_dlg_add_usage:添加一个模块作为dialog usages,并可以选择设置模块特定的数据。

pjsip_dlg_set_mod_data:将模块特定数据附加到dialog

pjsip_dlg_get_mod_data:获取先前附加到dialog的模块特定数据。

10.2.7 Dialog Request and Response API

pjsip_dlg_create_request:使用指定的方法创建一个基本/通用request,并可选地指定cseq。为cseq使用值-1,使dialog自动为请求放置下一个cseq编号。对于某些请求,例如CANCEL和ACK,应用程序必须将CSeq作为参数放入原始INVITE请求中。此函数还将在适当的位置放置Contact标题。

pjsip_dlg_send_request:发送请求消息到远程对等体。如果请求不是ACK请求,则dialog将创建UAC transaction并通过transaction将request 有状态地发送出去。此外,当请求不是ACK或CANCEL时,dialog将增加其本地cseq号,并根据dialog的cseq更新请求中的cseq。

pjsip_dlg_create_response:通过传入的rdata创建一条response消息,状态代码为st_code,可选状态文本为st_text。此函数与endpoint 的API pjsip_endpt_create_response()的不同之处在于,dialog函数在响应中适当地添加Contact标头和Record-Route标头。

pjsip_dlg_modify_response(:用其他状态代码修改以前发送的响应。将在适当时添加Contact header。

pjsip_dlg_send_response:有状态地发送响应消息。transaction实例必须是在on_req_request()回调中报告的transaction。

10.2.8 Dialog Auxiliary API

pjsip_dlg_set_route_set:

pjsip_dlg_start_app_timer

pjsip_dlg_stop_app_timer

pjsip_rdata_get_dlg:获取传入rdata中的dialog实例

pjsip_tsx_get_dlg:获取指定事务中的dialog实例

10.3 Examples

10.3.1 Invite UAS Dialog

o create and initialize incoming dialog, o create UAS transaction to process the incoming INVITE request and transmit 1xx responses, o transmit 2xx response to INVITE reliably, o process the incoming ACK.

Creating Initial Invite Dialog

pjsip_dlg_create_uas初始化uas、pjsip_dlg_add_usage将应用程序注册为唯一的dialog usage、pjsip_dlg_inc_session、pjsip_dlg_create_response创建180/振铃响应、pjsip_dlg_send_response有状态发送180响应、200/OK在answer_dlg()函数中,为此,我们必须“保存”INVITE事务。我们通过将事务实例放在dialog的模块数据中索引应用程序模块的ID来实现。

Answering Dialog

pjsip_dlg_modify_response修改之前的响应、pjsip_dlg_send_response使用以前的transaction发送200响应

Processing CANCEL Request

获取transaction、pjsip_dlg_respond、pjsip_dlg_modify_response 487 、pjsip_dlg_send_response、pjsip_dlg_dec_session

Processing ACK Request

pjsip_rdata_get_dlg、pjsip_rdata_get_dlg

10.3.2 Outgoing Invite Dialog

Creating Initial Dialog

pjsip_dlg_create_uac、pjsip_dlg_add_usage、pjsip_dlg_inc_session、pjsip_dlg_create_request(创建invite request)、pjsip_dlg_send_request(发送)

Receiving Response

pjsip_rdata_get_dlg、pjsip_rdata_get_tsx 获取rdata对应的transaction、根据transaction状态做不同的处理,send_ack/交给transaction处理

Sending ACK

pjsip_dlg_create_request、pjsip_dlg_send_request

10.3.3 Terminating Dialog

pjsip_dlg_create_request(Create BYE request)、pjsip_dlg_send_request (Send the request)、pjsip_dlg_dec_session

Chapter 11:SDP Offer/Answer Framework

PJSIP中的SDP提供/应答框架是基于RFC 3264“具有会话描述符协议(SDP)的提供/应答模型”。该框架的主要功能是本地和远程方之间的媒体能力的协商,并就在一个invite session中使用哪组媒体达成一致。

请注意,尽管它主要由invite session使用,但框架本身基于通用SDP协商框架(pjmedia_sdp_neg),因此它应该能够由其他类型的应用程序使用。dialog邀请会话提供SDP提供/应答框架与SIP协议的集成;它正确地解释相关消息(例如,INVITE、ACK、PRACK、UPDATE)中的消息体,并将它们转换为SDP提供/应答协商。

11.1 SDP Negotiator Structure

img

pjmedia_sdp_neg结构保留三个SDP结构

initial_sdp:这是本地endpoint的初始能力。此SDP在创建期间传递给negotiator,并且内容通常在整个会话期间不会更改(即使在协商之后)。当negotiator从远程接收到新的提议时(与从远程接收更新的SDP相反),negotiator在协商中使用该SDP。

active_local_sdp:包含与远程协商后的本地SDP。dialog必须使用这个启动它的本地媒体,而不是初始SDP。

active_remote_sdp:包含对等/远程当前使用的SDP。

11.2 SDP Negotiator Session

img

协商会话以PJMEDIA_SDP_NEG_STATE_NULL开始。如果dialog已准备好本地媒体描述并希望将媒体提供给远程(通常是dialog充当UAC时的情况),则它会通过将本地SDP传递给函数pjmedia_sdp_neg_create_w_local_offer()来创建SDP negotiator。此函数将设置本地endpoint的初始能力,并将协商会话状态设置为PJMEDIA_SDP_NEG_STATE_PROGRAM_OFFER。然后,可以在传出INVITE请求中将初始SDP发送到远程方。一旦dialog 收到远程的SDP,UAS端,它必须调用pjmedia_sdp_neg_remote_answer()并提供远程的SDP。然后可以调用协商函数。

如果dialog已经有了远程媒体描述(通常是当dialog充当UAS时的情况),它可以通过将本地和远程SDP传递给pjmedia_sdp_neg_create_w_remote_offer()来创建SDP协商器会话。在此之后,可以调用协商函数。

在会话建立之后,本地方和远程方都可以修改会话。negotiator可以处理以下两种情况之一:

  • Dialog已从远程接收SDP。在这种情况下,Dialog将调用pjmedia_sdp_neg_remote_offer()并将远程的SDP传递给此函数。在此之后,可以调用协商函数。协商函数的返回值确定本地媒体中是否需要修改。
  • 本地方希望将SDP发送到远程。dialog框可以进一步选择以下操作之一:
    • 如果它只想发送当前活动的本地SDP而不修改,它应该调用pjmedia_sdp_neg_tx_local_offer()来获取活动的本地SDP,发送SDP,然后等待远程的应答。
    • 如果它想要修改当前活动的本地媒体(例如,改变流方向,改变活动编解码器等),它应该使用pjmedia_sdp_neg_get_local()获取活动的本地媒体,修改它,调用pjmedia_sdp_neg_modify_local_offer()以更新报价,发送本地SDP,然后等待远程的应答。
    • dialog框可能希望完全更改本地媒体(例如更改IP地址、更改编解码器集、添加新媒体线)。这与上述更新当前媒体不同,因为它将更改initial_sdp,以便将来的协商将基于此新SDP。如果dialog框想要这样做,它会使用新的本地SDP调用pjmedia_sdp_neg_reinit_local_offer(),发送SDP,然后等待远程的应答。

Dialog向远程方发送要约后,它应该收到来自远程方的应答。Dialog必须向negotiator提供远程的SDP,以便可以调用协商功能。该Dialog通过调用pjsip_sdp_neg_remote_answer()提供远程应答。

如果远程拒绝了本地的提议(例如返回488/“此处不可接受”响应),dialog必须仍然调用pjsip_sdp_neg_remote_answer(),并在远程的SDP参数中提供NULL,并调用协商函数,以便negotiator会话可以恢复到先前活动的会话描述(如果有的话)。

11.3 SDP Negotiation Function

在提供了用于协商的本地和远程SDP之后,对话调用pjmedia_sdp_neg_negotiate()来协商提议和应答(即协商者状态为PJMEDIA_SDP_NEG_STATE_WAIT_NEGO)。此函数可能返回以下结果之一:

PJ_SUCCESS(即零),如果它已成功地在本地和远程SDP之间建立了协议。在这种情况下,本地和远程的活动SDP都将存储在会话中以供将来参考,应用程序可以查询这些活动SDP以启动本地媒体。

PJMEDIA_ESDPNOCHANGE,如果它发现当前使用的SDP(本地和远程)中不需要修改。在这种情况下,先前商定的SDP会话也不会被修改。

PJMEDIA_ESDPFAIL,如果它无法找到本地和远程功能的协议。在这种情况下,如果会话保留先前商定的SDP,则这些SDP(本地和远程)将不会被修改。如果dialog充当此会话的UAS,则它应该使用488/Not Accepted Here响应请求。

PJMEDIA_ESDPNOOFFER,如果negotiator尚未发送/接收任何offer

PJMEDIA_ESDPNOANSWER,如果negotiator尚未收到远程的应答。

在所有情况下,协商函数将negotiator’s状态设置为PJMEDIA_SDP_NEG_STATE_DONE。

Chapter 12:Dialog Invite Session and Usage

high level 的API application可以使用去管理invite session,application完全对抽象的dialog进行管理

dialog invite session由dialog invite usage管理,具体流程是,dialog invite usage将dialog收到的事件分发给invite session

12.1.1 Terms

Dialog invite session在dialog中

Dialog invite usage注册在dialog中

12.1.2 Features

  • Session progress reporting (e.g. session progressing, connected, confirmed, disconnected),
  • Automatic authentication handling (e.g. retry the request on receipt of 401/407 response),
  • SDP offer and answer handling,
  • High-level forking handler,
  • Session timeout (i.e. Expires header),
  • Session extensions, such as session timer, and reliable provisional response.

12.1.3 Invite Session State

dialog invite usage提供回调来通知应用程序有关会话进度。这对于电话应用程序特别有用,其中会话的状态通常与电话呼叫状态相关联。

img img

12.1.4 Invite Session Creation

对于outgoing dialog (i.e. caller),应用程序需要使用pjsip_dlg_create_uac()创建UAC对话。然后,应用程序通过调用pjsip_inv_create_uac()为对话创建invite session,并将UAC dialog实例作为参数之一传递。在invite session创建之前,应用程序不得发送INVITE请求,否则invite session将丢失某些事件。

对于 incoming dialog,应用程序可以首先通过调用pjsip_inv_verify_request()来验证请求是否可以接受。此函数验证Supported、Require和request body,以确保它可以接受请求。如果请求不能被接受,它将创建相应的拒绝响应。如果可以接受请求,则应用程序通过调用pjsip_dlg_create_uas()函数来创建UASdialog。然后,应用程序通过调用pjsip_inv_create_uas()为该对话创建invite session,并将UAS dialog实例作为参数之一传递。在invite session创建之前,应用程序不得发送任何响应,否则邀请会话将丢失某些事件。

Invite Session创建函数(即pjsip_inv_create_uac()和pjsip_inv_create_uas()函数)自动将invite session usage注册到dialog。应用程序不需要调用pjsip_dlg_add_usage()来将invite session usage注册到dialog。

12.1.5 Messages Handling

invite session处理可能更改 invite session状态的所有SIP方法。对于这个版本的PJSIP, invite session处理INVITE、BYE、ACK、CANCEL、UPDATE和PRACK方法。应用程序必须使用 invite session API创建和发送请求和响应消息与上述方法。这对于确保请求和响应消息得到正确处理以及包含session使用的适当功能(例如可靠的临时响应)是必要的。

12.1.6 Extending the Dialog

如前所述, invite session处理dialog中出现的INVITE、BYE、ACK、CANCEL、UPDATE和PRACK消息。当应用程序想要支持或处理其他类型的消息时,它必须将自己注册到dialog中作为 dialog usage。这将使应用程序能够处理传入的请求,这些请求是现有dialog的语言“未处理”的。应用程序正确设置其应用程序模块的优先级是很重要的。应用程序优先级应设置为PJSIP_MOD_PRIORITY_APPLICATION。邀请使用的模块优先级设置为(PJSIP_MOD_PRIORITY_APPLICATION-1)。这将确保invite用法能够在应用之前首先检查传入的请求。

12.1.7 Extending the Invite Session

在未来,邀请会话可能会扩展到支持更多的SIP扩展,如呼叫转移,对话目标等,目前,应用程序应该能够通过手动构建消息来执行这些功能。

12.2 Reference

12.2.1 Data Structure

/**

 * This enumeration describes invite session state.
   */
   typedef enum pjsip_inv_state
   {
   PJSIP_INV_STATE_NULL,           /**< Before INVITE is sent or received  */
   PJSIP_INV_STATE_CALLING,        /**< After INVITE is sent               */
   PJSIP_INV_STATE_INCOMING,       /**< After INVITE is received.          */
   PJSIP_INV_STATE_EARLY,          /**< After response with To tag.        */
   PJSIP_INV_STATE_CONNECTING,     /**< After 2xx is sent/received.        */
   PJSIP_INV_STATE_CONFIRMED,      /**< After ACK is sent/received.        */
   PJSIP_INV_STATE_DISCONNECTED,   /**< Session is terminated.             */
   } pjsip_inv_state;


/**

 * This structure describes the invite session.
   *
 * Note regarding the invite session's pools. The inv_sess used to have
 * only one pool, which is just a pointer to the dialog's pool. Ticket
 * https://github.com/pjsip/pjproject/issues/877 has found that the memory
 * usage will grow considerably everytime re-INVITE or UPDATE is
 * performed.
   *
 * Ticket #877 then created two more memory pools for the inv_sess, so
 * now we have three memory pools:
 * - pool: to be used to allocate long term data for the session
 * - pool_prov and pool_active: this is a flip-flop pools to be used
 * interchangably during re-INVITE and UPDATE. pool_prov is
 * "provisional" pool, used to allocate SDP offer or answer for
 * the re-INVITE and UPDATE. Once SDP negotiation is done, the
 * provisional pool will be made as the active pool, then the
 * existing active pool will be reset, to release the memory
 * back to the OS. So these pool's lifetime is synchronized to
 * the SDP offer-answer negotiation.
    *
 * Higher level application such as PJSUA-LIB has been modified to
 * make use of these flip-flop pools, i.e. by creating media objects
 * from the provisional pool rather than from the long term pool.
   *
 * Other applications that want to use these pools must understand
 * that the flip-flop pool's lifetimes are synchronized to the
 * SDP offer-answer negotiation.
   *
 * The lifetime of this session is controlled by the reference counter in this
 * structure, which is manipulated by calling #pjsip_inv_add_ref and
 * #pjsip_inv_dec_ref. When the reference counter has reached zero, then
 * this session will be destroyed.
   */
   struct pjsip_inv_session
   {
   char                 obj_name[PJ_MAX_OBJ_NAME]; /**< Log identification */
   pj_pool_t           *pool;                      /**< Long term pool.    */
   pj_pool_t           *pool_prov;                 /**< Provisional pool   */
   pj_pool_t           *pool_active;               /**< Active/current pool*/
   pjsip_inv_state      state;                     /**< Invite sess state. */
   pj_bool_t            cancelling;                /**< CANCEL requested   */
   pj_bool_t            pending_cancel;            /**< Wait to send CANCEL*/
   pjsip_tx_data       *pending_bye;               /**< BYE to send later  */
   pjsip_status_code    cause;                     /**< Disconnect cause.  */
   pj_str_t             cause_text;                /**< Cause text.        */
   pj_bool_t            notify;                    /**< Internal.          */
   pj_bool_t            sdp_done_early_rel;        /**< Nego done in early
                                                        med was reliable?  */
   unsigned             cb_called;                 /**< Cb has been called */
   pjsip_dialog        *dlg;                       /**< Underlying dialog. */
   pjsip_role_e         role;                      /**< Invite role.       */
   unsigned             options;                   /**< Options in use.    */
   pjmedia_sdp_neg     *neg;                       /**< Negotiator.        */
   unsigned             sdp_neg_flags;             /**< SDP neg flags.     */
   pjsip_transaction   *invite_tsx;                /**< 1st invite tsx.    */
   pjsip_tx_data       *invite_req;                /**< Saved invite req   */
   pjsip_tx_data       *last_answer;               /**< Last INVITE resp.  */
   pjsip_tx_data       *last_ack;                  /**< Last ACK request   */
   pj_int32_t           last_ack_cseq;             /**< CSeq of last ACK   */
   void                *mod_data[PJSIP_MAX_MODULE];/**< Modules data.      */
   struct pjsip_timer  *timer;                     /**< Session Timers.    */
   pj_bool_t            following_fork;            /**< Internal, following
                                                        forked media?      */
   pj_atomic_t         *ref_cnt;                   /**< Reference counter. */
   pj_bool_t            updated_sdp_answer;        /**< SDP answer just been
                                                        updated?           */
   };


下面的代码显示了可以应用于会话的各种选项。创建会话时需要指定这些选项的位掩码组合。在建立对话之后(包括早期),pjsip_inv_session的选项成员将显示哪些功能在两个端点中通用。

/**
 * This enumeration shows various options that can be applied to a session. 
 * The bitmask combination of these options need to be specified when 
 * creating a session. After the dialog is established (including early), 
 * the options member of #pjsip_inv_session shows which capabilities are 
 * common in both endpoints.
 */
enum pjsip_inv_option
{       
    /** 
     * Indicate support for reliable provisional response extension 
     */
    PJSIP_INV_SUPPORT_100REL    = 1,

    /** 
     * Indicate support for session timer extension. 
     */
    PJSIP_INV_SUPPORT_TIMER     = 2,

    /** 
     * Indicate support for UPDATE method. This is automatically implied
     * when creating outgoing dialog. After the dialog is established,
     * the options member of #pjsip_inv_session shows whether peer supports
     * this method as well.
     */
    PJSIP_INV_SUPPORT_UPDATE    = 4,

    /**
     * Indicate support for ICE
     */
    PJSIP_INV_SUPPORT_ICE       = 8,

    /**
     * Require ICE support.
     */
    PJSIP_INV_REQUIRE_ICE       = 16,

    /** 
     * Require reliable provisional response extension. 
     */
    PJSIP_INV_REQUIRE_100REL    = 32,

    /**  
     * Require session timer extension. 
     */
    PJSIP_INV_REQUIRE_TIMER     = 64,

    /**  
     * Session timer extension will always be used even when peer doesn't
     * support/want session timer.
     */
    PJSIP_INV_ALWAYS_USE_TIMER  = 128,

    /**
     * Indicate support for trickle ICE
     */
    PJSIP_INV_SUPPORT_TRICKLE_ICE = 256,

    /**
     * Require trickle ICE support.
     */
    PJSIP_INV_REQUIRE_TRICKLE_ICE = 512,

};

12.2.2 Invite Usage Module

在创建任何invite session之前,必须初始化invite dialog usage模块。

pjsip_inv_usage_init:初始化invite dialog usage模块并将其注册到endpoint。回调参数包含一个指针,指向在invite session中发生事件时要调用的函数(Session Callback)。

pjsip_inv_usage_instance:获取invite usage模块的实例。

12.2.3 Session Callback

usage 处理 session事件的函数

结构pjsip_inv_callback包含指向函数的指针,该函数将可以由应用程序注册到invite dialog usage模块以接收关于invite session事件的通知。

on_state_changed:当invite session状态更改时,将调用此回调。应用程序应该检查session状态(inv_sess->state)以获取当前状态。此回调是强制性的。

on_new_session:当invite使用模块创建了一个新的dialog并由于forked传出请求而发出invite时,将调用此回调。此回调是强制性的。

on_tsx_state_changed:每当dialog中的任何transaction更改了其状态时,都会调用此回调。应用程序可以实现此回调,例如,监控传出请求的进度。此回调是可选的。

on_rx_offer:当invite session收到来自对等方的新提议时,将调用此回调。应用程序通过调用pjsip_inv_set_sdp_answer()设置本地应答。此功能不会发送外发消息。它只是保留SDP协商过程的答案,并将包含在后续发送的响应或请求中。此回调是可选的。如果未指定,默认行为是与会话的初始功能协商远程提供。

on_media_update:此回调在SDP协商完后调用。status参数指定pjmedia_sdp_neg_negotiate()返回的offer/answer的状态。这个回调是可选的(从框架的角度来看),但是所有有用的应用程序通常都需要实现这个回调。

12.2.4 Session Creation and Termination

**pjsip_inv_create_uac:**在dlg中为指定的dialog创建UAC invite session。如果应用程序已确定其媒体能力,则可以在local_sdp中指定SDP。否则,它可以将其保留为NULL,以让远程UAS指定一个要约。选项参数是pjsip_inv_options枚举中SIP功能的位掩码组合。成功返回时,invite session将被放入inv_sess参数中,函数将返回PJ_SUCCESS。否则,失败时将返回相应的错误状态。

**pjsip_inv_verify_request:**应用程序应该在创建invite session(甚至对话)之前,在rdata中接收到初始INVITE请求时调用此函数,以验证nvite session可以处理INVITE请求。此功能验证本地endpoint是否能够处理请求中所需的SIP扩展(即Require报头字段)以及媒体(如果请求中存在媒体描述)。调用此函数时,选项参数SHOULD包含要应用于会话的所需SIP扩展。返回时,此参数将包含在考虑请求中的Supported、Require和Allow标头后将应用于会话的SIP扩展。如果本地媒体能力已经确定,并且如果应用程序希望验证它可以处理传入INVITE请求中的媒体提供,则应在local_sdp参数中指定其本地媒体能力。如果未指定,此功能将不执行介质验证。如果一切都协商成功,函数将返回PJ_SUCCESS。否则,它将返回失败的原因。此功能能够在验证失败时创建适当的响应消息。如果指定了tdata,那么当验证失败时,将创建一个非2xx的最终响应,并在返回时放入此参数中。如果在调用此函数之前已经创建了dialog,则必须在dlg参数中指定它。否则,应用程序必须指定endpt参数(例如,当应用程序想要无状态地发送响应时,这很有用)。

**pjsip_inv_create_uas:**在dlg中为指定的dialog创建UAS invite session。应用程序必须在rdata中指定收到的INVITE请求。邀请会话需要检查接收到的请求,以查看请求是否包含它支持的功能。应用程序应该在调用此函数之前调用验证函数,以确保它可以成功创建会话。如果应用程序已确定其媒体功能,则可以在local_sdp中指定此功能。如果在初始INVITE中接收到SDP,则local_sdp中指定的UAS功能不必与接收到的要约匹配; SDP协商器能够重新安排应答中的媒体线路,以便其与要约匹配。选项参数是pjsip_inv_options枚举中SIP功能的位掩码组合。成功返回时,邀请会话将被放入inv_sess参数中,函数将返回PJ_SUCCESS。否则,失败时将返回相应的错误状态。

**pjsip_inv_terminate:**提前终止INVITE会话并销毁基础dialog(如果该dialog没有其他用途)。只有在INVITE会话初始化失败时才应调用此函数。对于正常情况,应用程序必须通过调用pjsip_inv_end_session()来终止INVITE会话。st_code参数指定作为断开原因的SIP状态代码。如果notify为true,则将调用应用程序回调。

12.2.5 Session Operations

pjsip_inv_invite: 创建此会话的初始INVITE请求。

此函数只能在UAC会话中调用。如果初始INVITE请求可以成功创建,则它将被放入tdata参数中。如果在创建邀请会话时指定了本地媒体功能,则此函数将在传出的INVITE请求中放置SDP提议。否则,传出请求将不包含SDP主体。

pjsip_inv_answer: 创建对初始INVITE请求的响应消息。

st_code包含要发送的状态代码,它可以是临时的或最终的响应。如果需要自定义状态文本,应用程序可以在st_text中指定文本;否则,如果此参数为NULL,则将使用默认状态文本。 如果应用程序在创建UAS invite session期间指定了其媒体功能,则local_sdp参数必须为NULL。这是因为应用程序不能在单个INVITE transaction中执行多个SDP提供/应答会话。如果应用程序在创建UAS邀请会话期间没有指定其媒体能力,则它可以或必须在local_sdp参数中指定其能力,这取决于st_code是否指示2xx最终响应。

**pjsip_inv_end_session:**创建SIP消息以启动邀请会话终止。

根据会话的状态,此函数可能返回CANCEL请求、非2xx最终响应或BYE请求。如果会话没有应答传入的INVITE,此函数将创建非2xx最终响应,并在st_code中指定状态代码,在st_text中指定可选状态文本。

**pjsip_inv_reinvite:**创建re-INVITE请求。

如果应用程序想要更新其本地联系人并通知对等体使用新联系人执行目标刷新,则可以在new_contact参数中指定新联系人;否则此参数必须为NULL。当没有待发送或接收的应答时,应用程序可以在请求中发起新的SDP提供/应答会话。它可以通过观察邀请会话的SDP协商者的状态来检测这种情况。如果new offer应该发送到remote,则必须在new_offer中指定offer,否则此参数必须为NULL。

**pjsip_inv_update:**创建UPDATE请求。

如果应用程序想要更新其本地联系人并通知对等体使用新联系人执行目标刷新,则可以在new_contact参数中指定新联系人;否则此参数必须为NULL。当没有待发送或接收的应答时,应用程序可以在请求中发起新的SDP提供/应答会话。它可以通过观察邀请会话的SDP协商者的状态来检测这种情况。如果new offer应该发送到remote,则必须在new_offer中指定offer,否则此参数必须为NULL。

**pjsip_inv_send_msg:**在tdata中发送请求或响应消息。

令牌是一个任意的应用程序数据,它将被放置在事务的mod_data数组中,位于应用程序模块的索引处。

12.2.6 Auxiliary API

pjsip_dlg_get_inv_session:获取与对话dlg关联的invite session实例,或NULL。

pjsip_tsx_get_inv_session:获取与transaction tsx关联的invite session实例,或NULL。

State

转载:sip信令超时时间调整_pjsip的最长呼叫等待时间-CSDN博客

UAC (呼叫方)状态机转换如下:

img

刚开始呼叫时,sip_transaction的状态机处于tsx_on_state_null状态,拨打电话发出INVITE信令后,状态机转为Calling状态,处理函数:tsx_on_state_calling,应用可以收到Calling的状态通知。

之后会收到被叫方发来的100 try信令,状态机转为Processing状态,处理函数:tsx_on_state_proceeding_uac

在收到180 ringing的信令后,还是在tsx_on_state_proceeding_uac中处理,应用会收到Early的状态通知。

如果被叫方接听了该呼叫,会收到被叫方的200 OK信令,状态机会转为Terminated状态,处理函数:tsx_on_state_terminated,应用会收到Connecting的状态通知,之后会通过timeout方式,把状态转为Destroyed。Call的状态也转为Confirmed。

Bye信令状态转换:

img

发出Bye信令时,状态机转为Calling状态(tsx_on_state_calling),在收到200OK信令时,转为Completed状态(tsx_on_state_completed_uac),应用会收到Disconnected的状态通知

,之后通过timeout的方式,状态转为Terminated和Destroyed

UAS(被叫方)状态机转换如下:

*img*

Bye状态转换:

img

被叫方的转换和呼叫方类似。

make_call
pjsip_dlg_create_uac
on_make_call_med_tp_complete
tsx_on_state_null

发送数据全流程

1、发送的线程(在声音设备端直接获取声卡数据)

先看发送的线程 ca_thread_func,这里我们以alsa_dev为例

static int ca_thread_func (void *arg)
{
    struct alsa_stream* stream = (struct alsa_stream*) arg;
    snd_pcm_t* pcm             = stream->ca_pcm;
    int size                   = stream->ca_buf_size;
    snd_pcm_uframes_t nframes  = stream->ca_frames;
    void* user_data            = stream->user_data;
    char* buf                  = stream->ca_buf;
    pj_timestamp tstamp;
    int result;
    struct sched_param param;
    pthread_t* thid;

    thid = (pthread_t*) pj_thread_get_os_handle (pj_thread_this());
    param.sched_priority = sched_get_priority_max (SCHED_RR);
    PJ_LOG (5,(THIS_FILE, "ca_thread_func(%u): Set thread priority "
                          "for audio capture thread.",
                          (unsigned)syscall(SYS_gettid)));
    result = pthread_setschedparam (*thid, SCHED_RR, &param);
    if (result) {
        if (result == EPERM)
            PJ_LOG (5,(THIS_FILE, "Unable to increase thread priority, "
                                  "root access needed."));
        else
            PJ_LOG (5,(THIS_FILE, "Unable to increase thread priority, "
                                  "error: %d",
                                  result));
    }

    pj_bzero (buf, size);
    tstamp.u64 = 0;

    TRACE_((THIS_FILE, "ca_thread_func(%u): Started",
            (unsigned)syscall(SYS_gettid)));

    snd_pcm_prepare (pcm);

    while (!stream->quit) {
        pjmedia_frame frame;

        pj_bzero (buf, size);
        result = snd_pcm_readi (pcm, buf, nframes);
        if (result == -EPIPE) {
            PJ_LOG (4,(THIS_FILE, "ca_thread_func: overrun!"));
            snd_pcm_prepare (pcm);
            continue;
        } else if (result < 0) {
            PJ_LOG (4,(THIS_FILE, "ca_thread_func: error reading data!"));
        }
        if (stream->quit)
            break;

        frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
        frame.buf = (void*) buf;
        frame.size = size;
        frame.timestamp.u64 = tstamp.u64;
        frame.bit_info = 0;

        result = stream->ca_cb (user_data, &frame);
        if (result != PJ_SUCCESS || stream->quit)
            break;

        tstamp.u64 += nframes;
    }
    snd_pcm_drop(pcm);
    TRACE_((THIS_FILE, "ca_thread_func: Stopped"));

    return PJ_SUCCESS;
}

该线程的主体部分在不停的while循环,我们来看一次循环的内容:

调用 snd_pcm_readi (pcm, buf, nframes); 读网卡的数据到buf中。然后将读出的数据和一系列属性包装成一个frame。然后调用stream->ca_cb (user_data, &frame);

在初始化(pjmedia_aud_stream_create->f->op->create_stream)中我们知道stream->ca_cb 我们设置为 rec_cb 接下来我们来看rec_cb 在sound_port.c中.

另外我们需要知道ca_cb 中的user_data 到底是什么 这可以看我们之前初始化流程的分析:pjmedia_snd_port_create-》pjmedia_snd_port_create2-》start_sound_device-》pjmedia_aud_stream_create-》f->op->create_stream-》alsa_factory_create_stream

到pjmedia_aud_stream_create这步我们看出user_data是我们创建的snd_port

2、rec_cb (音频流端)

/*
 * The callback called by sound recorder when it has finished capturing a
 * frame.
 */
static pj_status_t rec_cb(void *user_data, pjmedia_frame *frame)
{
    pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
    pjmedia_port *port;

    pjmedia_clock_src_update(&snd_port->cap_clocksrc, &frame->timestamp);

    /* Invoke preview callback */
    if (snd_port->on_rec_frame)
        (*snd_port->on_rec_frame)(snd_port->user_data, frame);

    port = snd_port->port;
    if (port == NULL)
        return PJ_SUCCESS;

    /* Cancel echo */
    if (snd_port->ec_state && !snd_port->ec_suspended) {
        pjmedia_echo_capture(snd_port->ec_state, (pj_int16_t*) frame->buf, 0);
    }

    pjmedia_port_put_frame(port, frame);


    return PJ_SUCCESS;
}

首先看snd_port->on_rec_frame这个是可选项,这里没有使用

其次就是最重要的调用pjmedia_port_put_frame,看一下这里的port参数,port = snd_port->port; 是pjmedia_snd_port类型snd_port中的pjmedia_port类型属性,snd_port->port是在pjmedia_snd_port_connect中初始化的先看一下pjmedia_snd_port_connect

/*
 * Connect a port.
 */
PJ_DEF(pj_status_t) pjmedia_snd_port_connect( pjmedia_snd_port *snd_port,
                                              pjmedia_port *port)
{
    pjmedia_audio_format_detail *afd;

    PJ_ASSERT_RETURN(snd_port && port, PJ_EINVAL);

    afd = pjmedia_format_get_audio_format_detail(&port->info.fmt, PJ_TRUE);

    /* Check that port has the same configuration as the sound device
     * port.
     */
    if (afd->clock_rate != snd_port->clock_rate)
        return PJMEDIA_ENCCLOCKRATE;

    if (PJMEDIA_AFD_SPF(afd) != snd_port->samples_per_frame)
        return PJMEDIA_ENCSAMPLESPFRAME;

    if (afd->channel_count != snd_port->channel_count)
        return PJMEDIA_ENCCHANNEL;

    if (afd->bits_per_sample != snd_port->bits_per_sample)
        return PJMEDIA_ENCBITS;

    /* Port is okay. */
    snd_port->port = port;
    return PJ_SUCCESS;
}

注意这里的参数pjmedia_port *port来自pjmedia_stream stream的port,这个port还有一点特殊即port->port_data.pdata; 其实是指向stream,即stream中有port,port也有办法指向stream,这在put_frame中会遇到。

<::>再回来看pjmedia_port_put_frame该函数其实是port->put_frame callback的封装,会直接调用port->put_frame,这个回调函数的初始化在pjmedia_stream_create 完成将port->put_frame 初始化为 stream->port.put_frame = &put_frame;,所以接下来我们看put_frame


/**
 * Put a frame to the port (and subsequent downstream ports).
 */
PJ_DEF(pj_status_t) pjmedia_port_put_frame( pjmedia_port *port,
                                            pjmedia_frame *frame )
{
    PJ_ASSERT_RETURN(port && frame, PJ_EINVAL);

    if (port->put_frame)
        return port->put_frame(port, frame);
    else
        return PJ_EINVALIDOP;
}

3、put_frame (port端)

/**
 * put_frame()
 *
 * This callback is called by upstream component when it has PCM frame
 * to transmit. This function encodes the PCM frame, pack it into
 * RTP packet, and transmit to peer.
 */
static pj_status_t put_frame( pjmedia_port *port,
                              pjmedia_frame *frame )
{
    pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata;
    pjmedia_frame tmp_zero_frame;
    unsigned samples_per_frame;

    samples_per_frame = stream->enc_samples_per_pkt;

    /* https://github.com/pjsip/pjproject/issues/56:
     *  when input is PJMEDIA_FRAME_TYPE_NONE, feed zero PCM frame
     *  instead so that encoder can decide whether or not to transmit
     *  silence frame.
     */
    if (frame->type == PJMEDIA_FRAME_TYPE_NONE) {
        pj_memcpy(&tmp_zero_frame, frame, sizeof(pjmedia_frame));
        frame = &tmp_zero_frame;

        tmp_zero_frame.buf = NULL;
        tmp_zero_frame.size = samples_per_frame * 2;
        tmp_zero_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
    }

    /* If VAD is temporarily disabled during creation, enable it
     * after transmitting for VAD_SUSPEND_SEC seconds.
     */
    if (stream->vad_enabled != stream->codec_param.setting.vad &&
        (stream->tx_duration - stream->ts_vad_disabled) >
           PJMEDIA_PIA_SRATE(&stream->port.info) *
          PJMEDIA_STREAM_VAD_SUSPEND_MSEC / 1000)
    {
        stream->codec_param.setting.vad = stream->vad_enabled;
        pjmedia_codec_modify(stream->codec, &stream->codec_param);
        PJ_LOG(4,(stream->port.info.name.ptr,"VAD re-enabled"));
    }


    /* If encoder has different ptime than decoder, then the frame must
     * be passed through the encoding buffer via rebuffer() function.
     */
    if (stream->enc_buf != NULL) {
        pjmedia_frame tmp_rebuffer_frame;
        pj_status_t status = PJ_SUCCESS;

        /* Copy original frame to temporary frame since we need
         * to modify it.
         */
        pj_memcpy(&tmp_rebuffer_frame, frame, sizeof(pjmedia_frame));

        /* Loop while we have full frame in enc_buffer */
        for (;;) {
            pj_status_t st;

            /* Run rebuffer() */
            rebuffer(stream, &tmp_rebuffer_frame);

            /* Process this frame */
            st = put_frame_imp(port, &tmp_rebuffer_frame);
            if (st != PJ_SUCCESS)
                status = st;

            /* If we still have full frame in the buffer, re-run
             * rebuffer() with NULL frame.
             */
            if (stream->enc_buf_count >= stream->enc_samples_per_pkt) {

                tmp_rebuffer_frame.type = PJMEDIA_FRAME_TYPE_NONE;

            } else {

                /* Otherwise break */
                break;
            }
        }

        return status;

    } else {
        return put_frame_imp(port, frame);
    }
}

重点看一下put_frame_imp 对frame的处理,函数比较长,我们来分段看一下

  1. 函数开始时,从 port 参数中获取了指向 pjmedia_stream 结构的指针 stream,并且从 stream 中获取了编码通道 enc

        pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata;
        pjmedia_channel *channel = stream->enc;
    
  2. 如果流启用了保活机制,函数会检查距离上次发送数据包的时间间隔,如果超过了指定的保活间隔,会发送一个保活数据包。

  3. 然后,函数会根据帧的类型计算帧中的样本数 ts_len,并根据是否存在特定的编码器问题来确定 RTP 时间戳的长度 rtp_ts_len

        /* Number of samples in the frame */
        if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO)
            ts_len = ((unsigned)frame->size >> 1) /
                     stream->codec_param.info.channel_cnt;
        else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED)
            ts_len = PJMEDIA_PIA_SPF(&stream->port.info) /
                     PJMEDIA_PIA_CCNT(&stream->port.info);
        else
            ts_len = 0;
    
    #if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG!=0)
        /* Handle special case for audio codec with RTP timestamp inconsistence
         * e.g: G722, MPEG audio.
         */
        if (stream->has_g722_mpeg_bug)
            rtp_ts_len = stream->rtp_tx_ts_len_per_pkt;
        else
            rtp_ts_len = ts_len;
    #else
        rtp_ts_len = ts_len;
    #endif
    
  4. 如果编码通道被暂停,函数会更新 RTP 会话的时间戳,并在需要时发送 RTCP SR/RR 报告。

        /* Don't do anything if stream is paused, except updating RTP timestamp */
        if (channel->paused) {
            stream->enc_buf_pos = stream->enc_buf_count = 0;
    
            /* Update RTP session's timestamp. */
            status = pjmedia_rtp_encode_rtp( &channel->rtp, 0, 0, 0, rtp_ts_len,
                                             NULL, NULL);
    
            /* Update RTCP stats with last RTP timestamp. */
            stream->rtcp.stat.rtp_tx_last_ts = pj_ntohl(channel->rtp.out_hdr.ts);
    
            /* Check if now is the time to transmit RTCP SR/RR report.
             * We only do this when the decoder is paused,
             * because otherwise check_tx_rtcp() will be handled by on_rx_rtp().
             */
            if (stream->dec->paused) {
                check_tx_rtcp(stream, pj_ntohl(channel->rtp.out_hdr.ts));
            }
    
            return PJ_SUCCESS;
        }
    
  5. 接着,函数会增加传输时长,初始化输出帧缓冲区,并检查是否有 DTMF 数字在队列中,如果有则发送数字,否则对音频帧进行编码。

    	  if (stream->tx_dtmf_count) {
            int first=0, last=0;
    
            create_dtmf_payload(stream, &frame_out, 0, &first, &last);
    
            /* Encapsulate into RTP packet. Note that:
             *  - RTP marker should be set on the beginning of a new event
             *  - RTP timestamp is constant for the same packet.
             */
            status = pjmedia_rtp_encode_rtp( &channel->rtp,
                                             stream->tx_event_pt, first,
                                             (int)frame_out.size,
                                             (first ? rtp_ts_len : 0),
                                             (const void**)&rtphdr,
                                             &rtphdrlen);
    
            if (last) {
                /* This is the last packet for the event.
                 * Increment the RTP timestamp of the RTP session, for next
                 * RTP packets.
                 */
                inc_timestamp = stream->dtmf_duration +
                                ((DTMF_EBIT_RETRANSMIT_CNT-1) *
                                 stream->rtp_tx_ts_len_per_pkt)
                                - rtp_ts_len;
            }
    
        } 
    
  6. 如果音频帧的缓冲区为空,则发送一段静音,保持 NAT 绑定。

    	else if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO &&
                   frame->buf == NULL &&
                   stream->port.info.fmt.id == PJMEDIA_FORMAT_L16 &&
                   (stream->dir & PJMEDIA_DIR_ENCODING))
        {
            pjmedia_frame silence_frame;
    
            pj_bzero(&silence_frame, sizeof(silence_frame));
            silence_frame.buf = stream->zero_frame;
            silence_frame.size = stream->enc_samples_per_pkt * 2;
            silence_frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
            silence_frame.timestamp.u32.lo = pj_ntohl(stream->enc->rtp.out_hdr.ts);
    
            /* Encode! */
            status = pjmedia_codec_encode( stream->codec, &silence_frame,
                                           channel->out_pkt_size -
                                           sizeof(pjmedia_rtp_hdr),
                                           &frame_out);
            if (status != PJ_SUCCESS) {
                LOGERR_((stream->port.info.name.ptr, status,
                        "Codec encode() error"));
                return status;
            }
    
            /* Encapsulate. */
            status = pjmedia_rtp_encode_rtp( &channel->rtp,
                                             channel->pt, 0,
                                             (int)frame_out.size, rtp_ts_len,
                                             (const void**)&rtphdr,
                                             &rtphdrlen);
    
        
    } 
    
  7. 如果音频帧不为空,则对音频帧进行编码,并将 RTP 头封装到输出包中。

    else if ((frame->type == PJMEDIA_FRAME_TYPE_AUDIO &&
                    frame->buf != NULL) ||
                   (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED))
        {
            /* Encode! */
            status = pjmedia_codec_encode( stream->codec, frame,
                                           channel->out_pkt_size -
                                           sizeof(pjmedia_rtp_hdr),
                                           &frame_out);
            if (status != PJ_SUCCESS) {
                LOGERR_((stream->port.info.name.ptr, status,
                        "Codec encode() error"));
                return status;
            }
    
            /* Encapsulate. */
            status = pjmedia_rtp_encode_rtp( &channel->rtp,
                                             channel->pt, 0,
                                             (int)frame_out.size, rtp_ts_len,
                                             (const void**)&rtphdr,
                                             &rtphdrlen);
    
        } 
    
  8. 最后,函数会根据当前是否正在流式传输来设置 RTP 标记位,并将 RTP 包发送到传输层。

    
        /* Copy RTP header to the beginning of packet */
        pj_memcpy(channel->out_pkt, rtphdr, sizeof(pjmedia_rtp_hdr));
    
        /* Special case for DTMF: timestamp remains constant for
         * the same event, and is only updated after a complete event
         * has been transmitted.
         */
        if (inc_timestamp) {
            pjmedia_rtp_encode_rtp( &channel->rtp, stream->tx_event_pt, 0,
                                    0, inc_timestamp, NULL, NULL);
        }
    
        /* Set RTP marker bit if currently not streaming */
        if (stream->is_streaming == PJ_FALSE) {
            pjmedia_rtp_hdr *rtp = (pjmedia_rtp_hdr*) channel->out_pkt;
    
            rtp->m = 1;
            PJ_LOG(5,(stream->port.info.name.ptr,"Start talksprut.."));
        }
    
        stream->is_streaming = PJ_TRUE;
    
        /* Send the RTP packet to the transport. */
        status = pjmedia_transport_send_rtp(stream->transport, channel->out_pkt,
                                            frame_out.size +
                                                sizeof(pjmedia_rtp_hdr));
    
  9. 在发送 RTP 包之后,函数会更新一些统计信息,并且如果启用了保活机制,则记录最后发送数据包的时间。

我们来仔细梳理一下rtp包发送的流程

(1)先设置frame_out.buf 对应的偏置 ((char*)channel->out_pkt) + sizeof(pjmedia_rtp_hdr);

(2)调用pjmedia_codec_encode,对frame编码结果输出到frame_out中,此时frame_out.buf获取到rtp payload,相应地(char*)channel->out_pkt) + sizeof(pjmedia_rtp_hdr处获取到rtp payload

(3)添加rtp头部,调用pjmedia_rtp_encode_rtp,hannel->rtp->out_hdr,并将头部拷贝至(char*)channel->out_pkt)处,至此channel->out_pkt存放地为编码后的rtp包

(4)Send the RTP packet to the transport. 调用pjmedia_transport_send_rtp

我们就主要看pjmedia_transport_send_rtp了,其实就是op->send_rtp 的封装,由初始化可知send_rtp为transport_send_rtcp

PJ_INLINE(pj_status_t) pjmedia_transport_send_rtp(pjmedia_transport *tp,
                                                  const void *pkt,
                                                  pj_size_t size)
{
    return (*tp->op->send_rtp)(tp, pkt, size);
}

4、transport_send_rtp

直接调用transport_send_rtcp2

/* Called by application to send RTP packet */
static pj_status_t transport_send_rtp( pjmedia_transport *tp,
                                       const void *pkt,
                                       pj_size_t size)
{
    struct transport_udp *udp = (struct transport_udp*)tp;
    pj_ssize_t sent;
    unsigned id;
    struct pending_write *pw;
    pj_status_t status;

    /* Must be attached */
    //PJ_ASSERT_RETURN(udp->attached, PJ_EINVALIDOP);

    /* Check that the size is supported */
    PJ_ASSERT_RETURN(size <= PJMEDIA_MAX_MTU, PJ_ETOOBIG);

    if (!udp->started) {
        return PJ_SUCCESS;
    }

    /* Simulate packet lost on TX direction */
    if (udp->tx_drop_pct) {
        if ((pj_rand() % 100) <= (int)udp->tx_drop_pct) {
            PJ_LOG(5,(udp->base.name, 
                      "TX RTP packet dropped because of pkt lost "
                      "simulation"));
            return PJ_SUCCESS;
        }
    }


    id = udp->rtp_write_op_id;
    pw = &udp->rtp_pending_write[id];
    if (pw->is_pending) {
        /* There is still currently pending operation for this buffer. */
        PJ_LOG(4,(udp->base.name, "Too many pending write operations"));
        return PJ_EBUSY;
    }
    pw->is_pending = PJ_TRUE;

    /* We need to copy packet to our buffer because when the
     * operation is pending, caller might write something else
     * to the original buffer.
     */
    pj_memcpy(pw->buffer, pkt, size);

    sent = size;
    status = pj_ioqueue_sendto( udp->rtp_key, 
                                &udp->rtp_pending_write[id].op_key,
                                pw->buffer, &sent, 0,
                                &udp->rem_rtp_addr, 
                                udp->addr_len);

    if (status != PJ_EPENDING) {
        /* Send operation has completed immediately. Clear the flag. */
        pw->is_pending = PJ_FALSE;
    }

    udp->rtp_write_op_id = (udp->rtp_write_op_id + 1) %
                           PJ_ARRAY_SIZE(udp->rtp_pending_write);

    if (status==PJ_SUCCESS || status==PJ_EPENDING)
        return PJ_SUCCESS;

    return status;
}

udp->rtp_write_op_id;是当前write操作可用的id,udp->rtp_pending_write是Pending write对象用于指示udp->rtp_pending_write[udp->rtp_write_op_id]是否有pending的write 操作如果有pending 返回。没有的话将该id处置为pending pw->is_pending = PJ_TRUE; 调用pj_ioqueue_sendto 最后需要将id+1

5、pj_ioqueue_sendto

参数key来着 udp->rtp_key 在 pj_ioqueue_register_sock2中初始化,绑定到rtp socket

参数op_key来自 transport_udp::udp->rtp_pending_write[id].op_key,在transport_media_start中设置为空

如果key对应的writelist不为空,直接发送调用pj_sock_sendto

	 if (pj_list_empty(&key->write_list)) {
        /*
         * See if data can be sent immediately.
         */
        sent = *length;
        status = pj_sock_sendto(key->fd, data, &sent, flags, addr, addrlen);
        
    }

否则初始化write_operation:: write_op 主要需要将要发送的rtp数据保存在write_op中,然后挂在key对应的writelist队列上

		write_op->op = PJ_IOQUEUE_OP_SEND_TO;
    write_op->buf = (char*)data;
    write_op->size = *length;
    write_op->written = 0;
    write_op->flags = flags;
    pj_memcpy(&write_op->rmt_addr, addr, addrlen);
    write_op->rmt_addrlen = addrlen;
    
    pj_ioqueue_lock_key(key);
    /* Check again. Handle may have been closed after the previous check
     * in multithreaded app. If we add bad handle to the set it will
     * corrupt the ioqueue set. See #913
     */
    if (IS_CLOSING(key)) {
        pj_ioqueue_unlock_key(key);
        return PJ_ECANCELLED;
    }
    pj_list_insert_before(&key->write_list, write_op);

调用ioqueue_add_to_set进而调用ioqueue_add_to_set2

static void ioqueue_add_to_set2(pj_ioqueue_t *ioqueue,
                                pj_ioqueue_key_t *key,
                                unsigned event_types )
{
    pj_uint32_t events = key->ev.events;

    if (event_types & READABLE_EVENT)
        events |= EPOLLIN;
    if (event_types & WRITEABLE_EVENT)
        events |= EPOLLOUT;
    if (event_types & EXCEPTION_EVENT)
        events |= EPOLLERR;

    if (events != key->ev.events)
        update_epoll_event_set(ioqueue, key, events);
}

根据 event_types 设置 events 调用 update_epoll_event_set 这里event_types是WRITEABLE_EVENT

static void update_epoll_event_set(pj_ioqueue_t *ioqueue,
                                   pj_ioqueue_key_t *key,
                                   pj_uint32_t events)
{
    int rc;
    /* From epoll_ctl(2):
     * EPOLLEXCLUSIVE may be used only in an EPOLL_CTL_ADD operation;
     * attempts to employ it with EPOLL_CTL_MOD yield an error.
     */
    if (key->ev.events & EPOLLEXCLUSIVE) {
        rc = os_epoll_ctl(ioqueue->epfd, EPOLL_CTL_DEL, key->fd, &key->ev);
        key->ev.events = events;
        rc = os_epoll_ctl(ioqueue->epfd, EPOLL_CTL_ADD, key->fd, &key->ev);
    } else {
        key->ev.events = events;
        rc = os_epoll_ctl(ioqueue->epfd, EPOLL_CTL_MOD, key->fd, &key->ev);
    }

    if (rc != 0) {
        pj_status_t status = pj_get_os_error();
        PJ_PERROR(1,(THIS_FILE, status,
                     "epol_ctl(MOD) error (events=0x%x)", events));
    }
}

这里修改监测rtp socket事件EPOLL_CTL_MOD修改为EPOLLOUT 触发

6、ioqueue_epoll参与

这里不一定是上述追踪的rtp包

当pj_ioqueue_poll工作线程 调用os_epoll_wait 发现监测的EPOLLOUT写触发,调用ioqueue_dispatch_write_event写操作,在ioqueue_dispatch_write_event中先看key write_list上有没有pending_write,有的话,从write_list取出,根据write_list的 write_op确定写大小,要写入的数据,将数据写入调用pj_sock_send函数Transmit data to the socket.,最后调用on_write_complete,回调函数已在pj_ioqueue_register_sock2时设置过,传入write_op为on_rtp_data_sent

if (h->cb.on_write_complete && !IS_CLOSING(h)) {
                (*h->cb.on_write_complete)(h, 
                                           (pj_ioqueue_op_key_t*)write_op,
                                           write_op->written);
            }
static void on_rtp_data_sent(pj_ioqueue_key_t *key, 
                             pj_ioqueue_op_key_t *op_key, 
                             pj_ssize_t bytes_sent)
{
    struct transport_udp *udp;
    unsigned i;

    PJ_UNUSED_ARG(bytes_sent);

    udp = (struct transport_udp*) pj_ioqueue_get_user_data(key);

    for (i = 0; i < PJ_ARRAY_SIZE(udp->rtp_pending_write); ++i) {
        if (&udp->rtp_pending_write[i].op_key == op_key) {
            udp->rtp_pending_write[i].is_pending = PJ_FALSE;
            break;
        }
    }
}

遍历transport_udp udp中的rtp_pending_write 找到与目标 op_key一致的位置,将is_pending置为false完成

附:相互指向关系

stream.user_data-》snd_port,

snd_port.port-》port

port->port_data.pdata;-》stream

stream->transport-》pjmedia_transport

接收数据全流程

1、接受的线程(在声音设备端直接向声卡写入数据)

先看发送的线程 pb_thread_func,这里我们以alsa_dev为例

static int pb_thread_func (void *arg)
{
    struct alsa_stream* stream = (struct alsa_stream*) arg;
    snd_pcm_t* pcm             = stream->pb_pcm;
    int size                   = stream->pb_buf_size;
    snd_pcm_uframes_t nframes  = stream->pb_frames;
    void* user_data            = stream->user_data;
    char* buf                  = stream->pb_buf;
    pj_timestamp tstamp;
    int result;

    pj_bzero (buf, size);
    tstamp.u64 = 0;

    TRACE_((THIS_FILE, "pb_thread_func(%u): Started",
            (unsigned)syscall(SYS_gettid)));

    snd_pcm_prepare (pcm);

    while (!stream->quit) {
        pjmedia_frame frame;

        frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
        frame.buf = buf;
        frame.size = size;
        frame.timestamp.u64 = tstamp.u64;
        frame.bit_info = 0;

        result = stream->pb_cb (user_data, &frame);
        if (result != PJ_SUCCESS || stream->quit)
            break;

        if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
            pj_bzero (buf, size);

        result = snd_pcm_writei (pcm, buf, nframes);
        if (result == -EPIPE) {
            PJ_LOG (4,(THIS_FILE, "pb_thread_func: underrun!"));
            snd_pcm_prepare (pcm);
        } else if (result < 0) {
            PJ_LOG (4,(THIS_FILE, "pb_thread_func: error writing data!"));
        }

        tstamp.u64 += nframes;
    }

    snd_pcm_drop(pcm);
    TRACE_((THIS_FILE, "pb_thread_func: Stopped"));
    return PJ_SUCCESS;
}

该线程的主体部分在不停的while循环,我们来看一次循环的内容:

调用 stream->pb_cb (user_data, &frame); 其中user_data 依然是我们创建的snd_port,frame存放最终接收的一个frame即Frame to store samples.,需要先对frame的类型等信息进行设置。

调用 snd_pcm_writei (pcm, buf, nframes); 将数据写入声卡

2、play_cb (音频流端)

pjmedia/src/pjmedia/sound_port.c

/*
 * The callback called by sound player when it needs more samples to be
 * played.
 */
static pj_status_t play_cb(void *user_data, pjmedia_frame *frame)
{
    pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
    pjmedia_port *port;
    const unsigned required_size = (unsigned)frame->size;
    pj_status_t status;

    pjmedia_clock_src_update(&snd_port->play_clocksrc, &frame->timestamp);

    port = snd_port->port;
    if (port == NULL)
        goto no_frame;

    status = pjmedia_port_get_frame(port, frame);
    if (status != PJ_SUCCESS)
        goto no_frame;

    if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO)
        goto no_frame;

    /* Must supply the required samples */
    pj_assert(frame->size == required_size);

    if (snd_port->ec_state) {
        if (snd_port->ec_suspended) {
            snd_port->ec_suspended = PJ_FALSE;
            //pjmedia_echo_state_reset(snd_port->ec_state);
            PJ_LOG(4,(THIS_FILE, "EC activated"));
        }
        snd_port->ec_suspend_count = 0;
        pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)frame->buf);
    }

    /* Invoke preview callback */
    if (snd_port->on_play_frame)
        (*snd_port->on_play_frame)(snd_port->user_data, frame);

    return PJ_SUCCESS;

no_frame:
    frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
    frame->size = required_size;
    pj_bzero(frame->buf, frame->size);

    if (snd_port->ec_state && !snd_port->ec_suspended) {
        ++snd_port->ec_suspend_count;
        if (snd_port->ec_suspend_count > snd_port->ec_suspend_limit) {
            snd_port->ec_suspended = PJ_TRUE;
            PJ_LOG(4,(THIS_FILE, "EC suspended because of inactivity"));
        }
        if (snd_port->ec_state) {
            /* To maintain correct delay in EC */
            pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)frame->buf);
        }
    }

    /* Invoke preview callback */
    if (snd_port->on_play_frame)
        (*snd_port->on_play_frame)(snd_port->user_data, frame);

    return PJ_SUCCESS;
}

首先看snd_port->on_play_frame这个是可选项,这里没有使用

其次就是最重要的调用pjmedia_port_get_frame,看一下这里的port参数,port = snd_port->port; 是pjmedia_snd_port类型snd_port中的pjmedia_port类型属性,snd_port->port是在pjmedia_snd_port_connect中初始化的先看一下pjmedia_snd_port_connect

/*
 * Connect a port.
 */
PJ_DEF(pj_status_t) pjmedia_snd_port_connect( pjmedia_snd_port *snd_port,
                                              pjmedia_port *port)
{
    pjmedia_audio_format_detail *afd;

    PJ_ASSERT_RETURN(snd_port && port, PJ_EINVAL);

    afd = pjmedia_format_get_audio_format_detail(&port->info.fmt, PJ_TRUE);

    /* Check that port has the same configuration as the sound device
     * port.
     */
    if (afd->clock_rate != snd_port->clock_rate)
        return PJMEDIA_ENCCLOCKRATE;

    if (PJMEDIA_AFD_SPF(afd) != snd_port->samples_per_frame)
        return PJMEDIA_ENCSAMPLESPFRAME;

    if (afd->channel_count != snd_port->channel_count)
        return PJMEDIA_ENCCHANNEL;

    if (afd->bits_per_sample != snd_port->bits_per_sample)
        return PJMEDIA_ENCBITS;

    /* Port is okay. */
    snd_port->port = port;
    return PJ_SUCCESS;
}

注意这里的参数pjmedia_port *port来自pjmedia_stream stream的port,这个port还有一点特殊即port->port_data.pdata; 其实是指向stream,即stream中有port,port也有办法指向stream,这在put_frame中会遇到。

<::>再回来看pjmedia_port_put_frame该函数其实是port->put_frame callback的封装,会直接调用port->put_frame,这个回调函数的初始化在pjmedia_stream_create 完成将port->put_frame 初始化为 stream->get_frame = &get_frame;,所以接下来我们看put_frame

/**
 * Get a frame from the port (and subsequent downstream ports).
 */
PJ_DEF(pj_status_t) pjmedia_port_get_frame( pjmedia_port *port,
                                            pjmedia_frame *frame )
{
    PJ_ASSERT_RETURN(port && frame, PJ_EINVAL);

    if (port->get_frame)
        return port->get_frame(port, frame);
    else {
        frame->type = PJMEDIA_FRAME_TYPE_NONE;
        return PJ_EINVALIDOP;
    }
}

3、get_frame (port端)

pjmedia/src/pjmedia/stream.c

  1. 首先,函数从 port 中获取与当前流相关联的 streamchannel

        pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata;
        pjmedia_channel *channel = stream->dec;
    
  2. 然后,函数检查通道是否处于暂停状态,如果是,则设置帧类型为 PJMEDIA_FRAME_TYPE_NONE,表示没有帧可用,并返回 PJ_SUCCESS

        /* Return no frame is channel is paused */
        if (channel->paused) {
            frame->type = PJMEDIA_FRAME_TYPE_NONE;
            return PJ_SUCCESS;
        }
    
  3. 接着,函数检查是否处于软启动计数状态。如果是,它首先检查软启动计数是否为 PJMEDIA_STREAM_SOFT_START,如果是,则重置抖动缓冲区。然后递减软启动计数,并返回 PJ_SUCCESS

        if (stream->soft_start_cnt) {
            if (stream->soft_start_cnt == PJMEDIA_STREAM_SOFT_START) {
                PJ_LOG(4,(stream->port.info.name.ptr,
                          "Resetting jitter buffer in stream playback start"));
                pj_mutex_lock( stream->jb_mutex );
                pjmedia_jbuf_reset(stream->jb);
                pj_mutex_unlock( stream->jb_mutex );
            }
            --stream->soft_start_cnt;
            frame->type = PJMEDIA_FRAME_TYPE_NONE;
            return PJ_SUCCESS;
        }
    
  4. 接下来,函数从抖动缓冲区中获取帧并解码,直到获得足够的帧以满足编解码器的 ptime 要求。

    • 函数首先锁定抖动缓冲区的互斥锁。

      pj_mutex_lock( stream->jb_mutex );
      
    • 接着,函数计算所需的样本数,并根据解码器的 ptime 要求计算每帧的样本数。

          samples_required = PJMEDIA_PIA_SPF(&stream->port.info);
          samples_per_frame = stream->dec_ptime *
                              stream->codec_param.info.clock_rate *
                              stream->codec_param.info.channel_cnt /
                              stream->dec_ptime_denum /
                              1000;
          p_out_samp = (pj_int16_t*) frame->buf;
      
    • 然后,函数循环获取帧,直到获得足够的样本数。在每次循环中,它会尝试从抖动缓冲区获取帧pjmedia_jbuf_get_frame2放在channel->out_pkt,并根据获取的帧类型进行不同的处理。

          for (samples_count=0; samples_count < samples_required;) {
              char frame_type;
              pj_size_t frame_size = channel->out_pkt_size;
              pj_uint32_t bit_info;
      
              if (stream->dec_buf && stream->dec_buf_pos < stream->dec_buf_count) {
                  unsigned nsamples_req = samples_required - samples_count;
                  unsigned nsamples_avail = stream->dec_buf_count -
                                            stream->dec_buf_pos;
                  unsigned nsamples_copy = PJ_MIN(nsamples_req, nsamples_avail);
      
                  pjmedia_copy_samples(p_out_samp + samples_count,
                                       stream->dec_buf + stream->dec_buf_pos,
                                       nsamples_copy);
                  samples_count += nsamples_copy;
                  stream->dec_buf_pos += nsamples_copy;
                  continue;
              }
      
              /* Get frame from jitter buffer. */
              pjmedia_jbuf_get_frame2(stream->jb, channel->out_pkt, &frame_size,
                                      &frame_type, &bit_info);
      
      • 如果帧类型为 PJMEDIA_JB_MISSING_FRAME,表示丢失帧,则尝试激活 PLC 进行丢帧处理。如果 PLC 激活成功,则填充丢失的样本,并增加 PLC 计数。

      • 如果帧类型为 PJMEDIA_JB_ZERO_EMPTY_FRAME,表示抖动缓冲区为空。函数会尝试激活 PLC 进行丢帧处理,然后填充零样本,以平滑淡出。

      • 如果帧类型为 PJMEDIA_JB_ZERO_PREFETCH_FRAME,表示抖动缓冲区正在预取数据。函数会尝试激活 PLC 进行丢帧处理,然后填充零样本。

      • 如果帧类型为 PJMEDIA_JB_NORMAL_FRAME,表示获得了正常的帧。函数会解码帧pjmedia_codec_decode 得到frame_out并将其放入播放缓冲区即传入的参数frame中。

          /* Got "NORMAL" frame from jitter buffer */
          pjmedia_frame frame_in, frame_out;
          pj_bool_t use_dec_buf = PJ_FALSE;
        
          stream->plc_cnt = 0;
        
          /* Decode */
          frame_in.buf = channel->out_pkt;
          frame_in.size = frame_size;
          frame_in.bit_info = bit_info;
          frame_in.type = PJMEDIA_FRAME_TYPE_AUDIO;  /* ignored */
        
          frame_out.buf = p_out_samp + samples_count;
          frame_out.size = frame->size - samples_count*BYTES_PER_SAMPLE;
          if (stream->dec_buf &&
              bit_info * sizeof(pj_int16_t) > frame_out.size)
          {
              stream->dec_buf_pos = 0;
              stream->dec_buf_count = bit_info;
        
              use_dec_buf = PJ_TRUE;
              frame_out.buf = stream->dec_buf;
              frame_out.size = stream->dec_buf_size;
          }
        
          status = pjmedia_codec_decode( stream->codec, &frame_in,
                                         (unsigned)frame_out.size,
                                         &frame_out);
          if (status != 0) {
              LOGERR_((port->info.name.ptr, status,
                       "codec decode() error"));
        
              if (use_dec_buf) {
                  pjmedia_zero_samples(stream->dec_buf,
                                       stream->dec_buf_count);
              } else {
                  pjmedia_zero_samples(p_out_samp + samples_count,
                                       samples_per_frame);
              }
          } else if (use_dec_buf) {
              stream->dec_buf_count = (unsigned)frame_out.size /
                                      sizeof(pj_int16_t);
          }
        
          if (stream->jb_last_frm != frame_type) {
              /* Report changing frame type event */
              PJ_LOG(5,(stream->port.info.name.ptr,
                        "Jitter buffer starts returning normal frames "
                        "(after %d empty/lost)",
                        stream->jb_last_frm_cnt));
        
              stream->jb_last_frm = frame_type;
              stream->jb_last_frm_cnt = 1;
          } else {
              stream->jb_last_frm_cnt++;
          }
          if (!use_dec_buf)
              samples_count += samples_per_frame;
        
        
  5. 最后,函数解锁抖动缓冲区的互斥锁,并根据获取的样本数设置帧类型和大小,并返回 PJ_SUCCESS

(1)pjmedia_jbuf_get_frame3

/*
 * Get frame from jitter buffer.
 */
PJ_DEF(void) pjmedia_jbuf_get_frame3(pjmedia_jbuf *jb,
                                     void *frame,
                                     pj_size_t *size,
                                     char *p_frame_type,
                                     pj_uint32_t *bit_info,
                                     pj_uint32_t *ts,
                                     int *seq)
{
    if (jb->jb_prefetching) {

        /* Can't return frame because jitter buffer is filling up
         * minimum prefetch.
         */

        //pj_bzero(frame, jb->jb_frame_size);
        *p_frame_type = PJMEDIA_JB_ZERO_PREFETCH_FRAME;
        if (size)
            *size = 0;

        TRACE__((jb->jb_name.ptr, "GET prefetch_cnt=%d/%d",
                 jb_framelist_eff_size(&jb->jb_framelist), jb->jb_prefetch));

        jb->jb_empty++;

    } else {

        pjmedia_jb_frame_type ftype = PJMEDIA_JB_NORMAL_FRAME;
        pj_bool_t res;

        /* Try to retrieve a frame from frame list */
        res = jb_framelist_get(&jb->jb_framelist, frame, size, &ftype,
                               bit_info, ts, seq);
        if (res) {
            /* We've successfully retrieved a frame from the frame list, but
             * the frame could be a blank frame!
             */
            if (ftype == PJMEDIA_JB_NORMAL_FRAME) {
                *p_frame_type = PJMEDIA_JB_NORMAL_FRAME;
            } else {
                *p_frame_type = PJMEDIA_JB_MISSING_FRAME;
                jb->jb_lost++;
            }

            /* Store delay history at the first GET */
            if (jb->jb_last_op == JB_OP_PUT) {
                unsigned cur_size;

                /* We've just retrieved one frame, so add one to cur_size */
                cur_size = jb_framelist_eff_size(&jb->jb_framelist) + 1;
                pj_math_stat_update(&jb->jb_delay,
                                    cur_size * jb->jb_frame_ptime /
                                    jb->jb_frame_ptime_denum);
            }
        } else {
            /* Jitter buffer is empty */
            if (jb->jb_prefetch)
                jb->jb_prefetching = PJ_TRUE;

            //pj_bzero(frame, jb->jb_frame_size);
            *p_frame_type = PJMEDIA_JB_ZERO_EMPTY_FRAME;
            if (size)
                *size = 0;

            jb->jb_empty++;
        }
    }

    jb->jb_level++;
    jbuf_update(jb, JB_OP_GET);
}

  1. 首先,函数检查抖动缓冲区是否正在预取数据。如果是,则表示抖动缓冲区正在填充,此时无法返回帧,因此将帧类型设置为 PJMEDIA_JB_ZERO_PREFETCH_FRAME,并将帧大小设置为 0。
  2. 如果抖动缓冲区不在预取状态,则尝试从帧列表中获取帧。如果成功获取到帧,则将帧类型设置为 PJMEDIA_JB_NORMAL_FRAME,表示正常帧。如果获取到的是空白帧,则将帧类型设置为 PJMEDIA_JB_MISSING_FRAME,并增加抖动缓冲区丢失帧的计数。
  3. 如果无法从帧列表中获取帧,表示抖动缓冲区为空。如果抖动缓冲区允许预取,则将预取状态设置为 PJ_TRUE。然后,将帧类型设置为 PJMEDIA_JB_ZERO_EMPTY_FRAME,并将帧大小设置为 0。
  4. 最后,函数更新抖动缓冲区的级别,并调用 jbuf_update 函数更新抖动缓冲区的操作。

4、ioqueue_epoll参与

这里不一定是上述追踪的rtp包

当pj_ioqueue_poll工作线程 调用os_epoll_wait 发现监测的读触发,调用ioqueue_dispatch_read_event写操作,在ioqueue_dispatch_read_event中先看key read_list上有没有pending_read,有的话,从read_list取出,根据read_list的read_op确定读入大小,pj_sock_recvfrom接受数据的函数,将数据读入到read_op中,最后调用on_read_complete,回调函数已在pj_ioqueue_register_sock2时设置过,传入read_op为 on_rx_rtp

(*h->cb.on_read_complete)(h, 
                                      (pj_ioqueue_op_key_t*)read_op,
                                      bytes_read);

(1)on_rx_rtp 读取操作

on_rx_rtp是被下面调用

(*h->cb.on_read_complete)(h, 
                                      (pj_ioqueue_op_key_t*)read_op,
                                      bytes_read);

这里有一个很有意思的问题,就是read_op 在on_rx_rtp中竟然没有使用,下面来分析一下原因

udp = (struct transport_udp*) pj_ioqueue_get_user_data(key);

我们的read_op是从readlist中取出的,readlist对于读操作添加是依靠pj_ioqueue_recvfrom函数,在data is not immediately available时将read_op加入readlist

read_op->op = PJ_IOQUEUE_OP_RECV_FROM;
read_op->buf = buffer;
read_op->size = *length;
read_op->flags = flags;
read_op->rmt_addr = addr;
read_op->rmt_addrlen = addrlen;

pj_ioqueue_lock_key(key);
/* Check again. Handle may have been closed after the previous check
 * in multithreaded app. If we add bad handle to the set it will
 * corrupt the ioqueue set. See #913
 */
if (IS_CLOSING(key)) {
    pj_ioqueue_unlock_key(key);
    return PJ_ECANCELLED;
}
pj_list_insert_before(&key->read_list, read_op);
ioqueue_add_to_set(key->ioqueue, key, READABLE_EVENT);

这里read_op->buf = buffer;的buffer,来自udp->rtp_pkt,相当于直接写入了rtp_pkt,所以不用read_op了。

on_rx_rtp是一个while循环,条件如下status来自pj_ioqueue_recvfrom的结果

status != PJ_EPENDING && status != PJ_ECANCELLED &&
             udp->started

a. call_rtp_cb

在while循环里,先执行call_rtp_cb,设置pjmedia_tp_cb_param param;,调用(*cb2)(&param);cb2由transport_attach2-》tp_attach设置为stream.c ::on_rx_rtp。注意param.pkt = udp->rtp_pkt;,这里rtp_pkt其实就是ioqueue_dispatch_read_event中read_op->buf中读到的数据rtp包

/* Call RTP cb. */
static void call_rtp_cb(struct transport_udp *udp, pj_ssize_t bytes_read, 
                        pj_bool_t *rem_switch)
{
    void (*cb)(void*,void*,pj_ssize_t);
    void (*cb2)(pjmedia_tp_cb_param*);
    void *user_data;

    cb = udp->rtp_cb;
    cb2 = udp->rtp_cb2;
    user_data = udp->user_data;

    if (cb2) {
        pjmedia_tp_cb_param param;

        param.user_data = user_data;
        param.pkt = udp->rtp_pkt;
        param.size = bytes_read;
        param.src_addr = &udp->rtp_src_addr;
        param.rem_switch = PJ_FALSE;
        (*cb2)(&param);
        if (rem_switch)
            *rem_switch = param.rem_switch;
    } else if (cb) {
        (*cb)(user_data, udp->rtp_pkt, bytes_read);
    }
}

param.user_data = user_data; 注意这个user_data,是pjmedia_stream *stream

b. pj_ioqueue_recvfrom

/*
 * pj_ioqueue_recvfrom()
 *
 * Start asynchronous recvfrom() from the socket.
 */
PJ_DEF(pj_status_t) pj_ioqueue_recvfrom( pj_ioqueue_key_t *key,
                                         pj_ioqueue_op_key_t *op_key,
                                         void *buffer,
                                         pj_ssize_t *length,
                                         unsigned flags,
                                         pj_sockaddr_t *addr,
                                         int *addrlen)
{
    struct read_operation *read_op;

    PJ_ASSERT_RETURN(key && op_key && buffer && length, PJ_EINVAL);
    PJ_CHECK_STACK();

    /* Check if key is closing. */
    if (IS_CLOSING(key))
        return PJ_ECANCELLED;

    read_op = (struct read_operation*)op_key;
    PJ_ASSERT_RETURN(read_op->op == PJ_IOQUEUE_OP_NONE, PJ_EPENDING);
    read_op->op = PJ_IOQUEUE_OP_NONE;

    /* Try to see if there's data immediately available. 
     */
    if ((flags & PJ_IOQUEUE_ALWAYS_ASYNC) == 0) {
        pj_status_t status;
        pj_ssize_t size;

        size = *length;
        status = pj_sock_recvfrom(key->fd, buffer, &size, flags,
                                  addr, addrlen);
        if (status == PJ_SUCCESS) {
            /* Yes! Data is available! */
            *length = size;
            return PJ_SUCCESS;
        } else {
            /* If error is not EWOULDBLOCK (or EAGAIN on Linux), report
             * the error to caller.
             */
            if (status != PJ_STATUS_FROM_OS(PJ_BLOCKING_ERROR_VAL))
                return status;
        }
    }

    flags &= ~(PJ_IOQUEUE_ALWAYS_ASYNC);

    /*
     * No data is immediately available.
     * Must schedule asynchronous operation to the ioqueue.
     */
    read_op->op = PJ_IOQUEUE_OP_RECV_FROM;
    read_op->buf = buffer;
    read_op->size = *length;
    read_op->flags = flags;
    read_op->rmt_addr = addr;
    read_op->rmt_addrlen = addrlen;

    pj_ioqueue_lock_key(key);
    /* Check again. Handle may have been closed after the previous check
     * in multithreaded app. If we add bad handle to the set it will
     * corrupt the ioqueue set. See #913
     */
    if (IS_CLOSING(key)) {
        pj_ioqueue_unlock_key(key);
        return PJ_ECANCELLED;
    }
    pj_list_insert_before(&key->read_list, read_op);
    ioqueue_add_to_set(key->ioqueue, key, READABLE_EVENT);
    pj_ioqueue_unlock_key(key);

    return PJ_EPENDING;
}

接下来是调用pj_ioqueue_recvfrom,至于为什么明明ioqueue_dispatch_read_event已经读取了数据,此时还在读取数据,是因为可能有新的rtp包到达,pj_ioqueue_recvfrom查看有没有到达的包,如果有就调用pj_sock_recvfrom继续读读到udp->rtp_pkt,如果没有加到readlist中,返回PJ_EPENDING,结束on_rx_rtp中的while循环。

(2)on_rx_rtp::cb2

Stream.c中的回调 tp_attach中设置该回调

该函数处理接收到的rtp包, 解析成payload和head

Put "good" packet to jitter buffer,需要先把payload解析成frame,再把frame放入jitter buffer

附:相互指向关系

stream.user_data-》snd_port,

snd_port.port-》port

port->port_data.pdata-》stream

stream->transport-》pjmedia_transport

初始化全流程思考

一个transport 一个 stream一个port两个channel,

img

一、再开启invite session之前

  1. 创建media endpoint
  2. 创建transport放在g_med_transport数组,注册绑定socket

二、loop处理sip事件

三、SDP协商完后回调

  1. 创建stream,先获取stream_info(从sdp协商中pjmedia_stream_info_from_sdp)

    • 1、申请媒体流空间

      2、初始化流的若干参数

      3、codec管理者及codec相关的操作

      4、设置第一组回调put_frame和get_frame,这组回调是音频设备port要用的

      5、创建jitterbuffer,这个后面会单独讲

      6、创建编码通道和解码通道

      7、调用上一节中提到的媒体传输attach,传入第2组回调on_rx_rtp和on_rx_rtcp

  2. pjmedia_stream_start,channel开,Start the audio stream

    编码通道和解码通道pause =0;

  3. pjmedia_transport_media_start,UDP media transport开

    Start the audio stream,tp->start = 1

  4. port创建,创建设备,开启设备 Open audio stream object

Callback when SDP negotiation has completed.

pjmedia总述

转载:pjmedia_HYQ458941968的博客-CSDN博客

从对象关系来看:

一次session,多个endpoint参与,彼此发送stream,每个stream两个channel分别收发。音频设备port对象采集播放声音,transport最终发送接收信号。

1、 pjmedia_endpt,代表一个媒体端点,端点可以理解为一个节点,可以是服务器或者客户端,一个设备一般只会有唯一一个端点,而且在初始化的时候创建。

2、pjmedia_session,代表一次会话,一个会话可以只有两个,也可以有多个参与者会议。

3、pjmedia_stream,代表一个流,在于一方进行通讯时,一般可以有音频流、视频流。

4、pjmedia_channel,只有一个方向的媒体流,也就是说,对应音频流,要发送通道和接收通道两个channel。

以上是范围从大到小的媒体对象,下面介绍其它对象。

5、pjmedia_transport,传输对象,封装了所有从网络接收数据和发送数据到网络的操作,可以是UDP、SRTP、ICE等传输方式。

6、pjmedia_snd_port,音频设备对象,最终的音频裸流都要在设备上播放,从麦克风采集,此对象封装所有设备操作。

从数据流来看:

img

左边为最底层,音频设备的播放和采集,通过回调接口,调用stream.c生产或消化数据,最后通过transport传输接口进行发送接收。当然,其中还夹杂着前后处理、编解码、抖动缓冲区等,后面的章节会对各个对象及相关的音频处理进行描述。

最后来分析一下实例代码simpleua.c中对pjmedia的使用方法。

初始化

在main函数中,前半部分主要是对sip的初始化,对pjmedia的初始化大概从358行开始

1、创建媒体端点endpoint

static pjmedia_endpt	    *g_med_endpt;   /* Media endpoint.		*/
 
#if PJ_HAS_THREADS
    status = pjmedia_endpt_create(&cp.factory, NULL, 1, &g_med_endpt);
#else
    status = pjmedia_endpt_create(&cp.factory, 
				  pjsip_endpt_get_ioqueue(g_endpt), 
				  0, &g_med_endpt);
#endif

这里我们默认看有多线程的流程。

2、创建udp网络接口transport

static pjmedia_transport    *g_med_transport[MAX_MEDIA_CNT];
   
    /* 
     * 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.
     */
    for (i = 0; i < PJ_ARRAY_SIZE(g_med_transport); ++i) {
	status = pjmedia_transport_udp_create3(g_med_endpt, AF, NULL, NULL, 
					       RTP_PORT + i*2, 0, 
					       &g_med_transport[i]);

可以看出,虽然初始化还没有建立媒体通讯,但是预先创建了若干传输对象。

3、loop处理sip事件

/* Loop until one call is completed */
for (;!g_complete;) {
	pj_time_val timeout = {0, 10};
	pjsip_endpt_handle_events(g_endpt, &timeout);
}

初始化的最后是一个循环等待处理sip对象的操作。

建立媒体通讯

当sip,sdp协商成功后,则要开始媒体通讯,发生在回调函数call_on_media_update

1、创建并启动流对象stream

static pjmedia_stream       *g_med_stream;  /* Call's audio stream.	*/
    
    /* Create stream info based on the media audio SDP. */
    status = pjmedia_stream_info_from_sdp(&stream_info, inv->dlg->pool,
					  g_med_endpt,
					  local_sdp, remote_sdp, 0);
    
    /* Create new audio media stream, passing the stream info, and also the
     * media socket that we created earlier.
     */
    status = pjmedia_stream_create(g_med_endpt, inv->dlg->pool, &stream_info,
				   g_med_transport[0], NULL, &g_med_stream);
 
    /* Start the audio stream */
    status = pjmedia_stream_start(g_med_stream);

这个实例并没有创建session,而是直接创建流对象。先从sip协商的sdp中获取媒体信息pjmedia_stream_info_from_sdp,然后根据这些信息创建流对象pjmedia_stream_create,并紧接着启动流pjmedia_stream_start。这里只有音频流,视频流暂不纳入分析范围。

2、启动网络传输transport

    /* Start the UDP media transport */
    pjmedia_transport_media_start(g_med_transport[0], 0, 0, 0, 0);

前面讲到,初始化的时候预创建了若干传输对象,但是并没有启动,等到协商成功后,才启动网络传输。

3、创建音频设备对象Sound_device

static pjmedia_snd_port	    *g_snd_port;    /* Sound device.		*/
 
    /* Get the media port interface of the audio stream. 
     * Media port interface is basicly a struct containing get_frame() and
     * put_frame() function. With this media port interface, we can attach
     * the port interface to conference bridge, or directly to a sound
     * player/recorder device.
     */
    pjmedia_stream_get_port(g_med_stream, &media_port);
 
    /* Create sound port */
    pjmedia_snd_port_create(inv->pool,
                            PJMEDIA_AUD_DEFAULT_CAPTURE_DEV,
                            PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV,
                            PJMEDIA_PIA_SRATE(&media_port->info),/* clock rate	    */
                            PJMEDIA_PIA_CCNT(&media_port->info),/* channel count    */
                            PJMEDIA_PIA_SPF(&media_port->info), /* samples per frame*/
                            PJMEDIA_PIA_BITS(&media_port->info),/* bits per sample  */
                            0,
                            &g_snd_port);
 
    status = pjmedia_snd_port_connect(g_snd_port, media_port);

这里先从流对象取出媒体接口pjmedia_stream_get_port,其中的媒体接口包含了数据回调,这些后面分析数据流再重点讲,然后创建pjmedia_snd_port_create并启动pjmedia_snd_port_connect音频设备对象。

结束销毁

    /* Destroy audio ports. Destroy the audio port first
     * before the stream since the audio port has threads
     * that get/put frames to the stream.
     */
    if (g_snd_port)
			pjmedia_snd_port_destroy(g_snd_port);
 
 
    /* Destroy streams */
    if (g_med_stream)
      pjmedia_stream_destroy(g_med_stream);
 
    /* Destroy media transports */
    for (i = 0; i < MAX_MEDIA_CNT; ++i) {
      if (g_med_transport[i])
          pjmedia_transport_close(g_med_transport[i]);
    }

        /* Deinit pjmedia endpoint */
    if (g_med_endpt)
      pjmedia_endpt_destroy(g_med_endpt);

        /* Deinit pjsip endpoint */
    if (g_endpt)
      pjsip_endpt_destroy(g_endpt);

        /* Release pool */
    if (pool)
      pj_pool_release(pool);

当主线程退出loop时,则销毁所有创建的对象。

pjmedia_endpt

simpleua.c在进行媒体相关初始化时,首先创建媒体端点,看看媒体端点的数据结构和创建流程。

#if PJ_HAS_THREADS
    status = pjmedia_endpt_create(&cp.factory, NULL, 1, &g_med_endpt);
#else
    status = pjmedia_endpt_create(&cp.factory, 
				  pjsip_endpt_get_ioqueue(g_endpt), 
				  0, &g_med_endpt);
#endif

pjmedia_endpt结构体

/** Concrete declaration of media endpoint. */
struct pjmedia_endpt
{
    /** Pool. */
    pj_pool_t		 *pool;
 
    /** Pool factory. */
    pj_pool_factory	 *pf;
 
    /** Codec manager. */
    pjmedia_codec_mgr	  codec_mgr;
 
    /** IOqueue instance. */
    pj_ioqueue_t 	 *ioqueue;
 
    /** Do we own the ioqueue? */
    pj_bool_t		  own_ioqueue;
 
    /** Number of threads. */
    unsigned		  thread_cnt;
 
    /** IOqueue polling thread, if any. */
    pj_thread_t		 *thread[MAX_THREADS];
 
    /** To signal polling thread to quit. */
    pj_bool_t		  quit_flag;
 
    /** Is telephone-event enable */
    pj_bool_t		  has_telephone_event;
 
    /** List of exit callback. */
    exit_cb		  exit_cb_list;
};

ioqueue:用于绑定socket,然后异步接收数据;

thread[]:线程数组,存储工作线程。

codec_mgr:codec的管理者

exit_cb_list:退出时的回调链表

quit_flag:置位则工作线程都退出

创建端点

/**
 * Initialize and get the instance of media endpoint.
 */
PJ_DEF(pj_status_t) pjmedia_endpt_create2(pj_pool_factory *pf,
					  pj_ioqueue_t *ioqueue,
					  unsigned worker_cnt,
					  pjmedia_endpt **p_endpt)
{
    pj_pool_t *pool;
    pjmedia_endpt *endpt;
    unsigned i;
    pj_status_t status;
 
 
    pool = pj_pool_create(pf, "med-ept", 512, 512, NULL);
    if (!pool)
			return PJ_ENOMEM;
 
    endpt = PJ_POOL_ZALLOC_T(pool, struct pjmedia_endpt);
    endpt->pool = pool;
    endpt->pf = pf;
    endpt->ioqueue = ioqueue;
    endpt->thread_cnt = worker_cnt;
    endpt->has_telephone_event = PJ_TRUE;
 
 
    /* Init codec manager. */
    status = pjmedia_codec_mgr_init(&endpt->codec_mgr, endpt->pf);
    if (status != PJ_SUCCESS)
			goto on_error;
 
    /* Initialize exit callback list. */
    pj_list_init(&endpt->exit_cb_list);
 
    /* Create ioqueue if none is specified. */
    if (endpt->ioqueue == NULL) {
	
    endpt->own_ioqueue = PJ_TRUE;

    status = pj_ioqueue_create( endpt->pool, PJ_IOQUEUE_MAX_HANDLES,
              &endpt->ioqueue);
    if (status != PJ_SUCCESS)
        goto on_error;

    }
 
    /* Create worker threads if asked. */
    for (i=0; i<worker_cnt; ++i) {
      status = pj_thread_create( endpt->pool, "media", &worker_proc,
               endpt, 0, 0, &endpt->thread[i]);
      if (status != PJ_SUCCESS)
          goto on_error;
    }
 
 
    *p_endpt = endpt;
    return PJ_SUCCESS;
 
on_error:
 
    /* Destroy threads */
    for (i=0; i<endpt->thread_cnt; ++i) {
      if (endpt->thread[i]) {
          pj_thread_destroy(endpt->thread[i]);
      }
    }
 
    /* Destroy internal ioqueue */
    if (endpt->ioqueue && endpt->own_ioqueue)
			pj_ioqueue_destroy(endpt->ioqueue);
 
    pjmedia_codec_mgr_destroy(&endpt->codec_mgr);
    //pjmedia_aud_subsys_shutdown();
    pj_pool_release(pool);
    return status;
}

1、创建内存池med-ept

2、申请媒体端点结构体内存,并把外部的ioqueue地址存到结构体 endpt->ioqueue = ioqueue;

3、初始化编码器manager pjmedia_codec_mgr_init(&endpt->codec_mgr, endpt->pf)

4、如果初入的ioqueue为空,则创建一个新的ioqueue

5、创建工作线程组,线程函数是worker_proc

工作线程

/**
 * Worker thread proc.
 */
static int PJ_THREAD_FUNC worker_proc(void *arg)
{
    pjmedia_endpt *endpt = (pjmedia_endpt*) arg;
 
    while (!endpt->quit_flag) {
        pj_time_val timeout = { 0, 500 };
        pj_ioqueue_poll(endpt->ioqueue, &timeout);
    }
 
    return 0;
}

工作线程就是每500ms调用一次pj_ioqueue_pool。刚创建媒体端点时,ioqueue还没有注册socket,所以不会监控到数据。

pjmedia_transport

媒体传输封装了网络收发细节,pjmedia_transport可以是udp、srtp、ice等,这里以udp为例。

结构体pjmedia_transport

/**
 * This structure declares media transport. A media transport is called
 * by the stream to transmit a packet, and will notify stream when
 * incoming packet is arrived.
 */
struct pjmedia_transport
{
    /** Transport name (for logging purpose). */
    char		     name[PJ_MAX_OBJ_NAME];
 
    /** Transport type. */
    pjmedia_transport_type   type;
 
    /** Transport's "virtual" function table. */
    pjmedia_transport_op    *op;
 
    /** Application/user data */
    void		    *user_data;
};

type:传输类型,上面讲过,这里以udp为例。

op:操作集,每种传输类型实现了同一组接口。

user_data:应用层用户数据

pjmedia_transport_op

操作集是核心,这里列举重要的一些函数。pjmedia_transport_op

/**
 * This structure describes the operations for the stream transport.
 */
struct pjmedia_transport_op
{
 
    /**
     * This function is called by the stream when the transport is about
     * to be used by the stream for the first time, and it tells the transport
     * about remote RTP address to send the packet and some callbacks to be 
     * called for incoming packets. This function exists for backwards
     * compatibility. Transports should implement attach2 instead.
     *
     * Application should call #pjmedia_transport_attach() instead of 
     * calling this function directly.
     */
    pj_status_t (*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 size),
			  void (*rtcp_cb)(void *user_data,
					  void *pkt,
					  pj_ssize_t size));
 
    /**
     * This function is called by the stream to send RTP packet using the 
     * transport.
     *
     * Application should call #pjmedia_transport_send_rtp() instead of 
     * calling this function directly.
     */
    pj_status_t (*send_rtp)(pjmedia_transport *tp,
			    const void *pkt,
			    pj_size_t size);
 
    /**
     * Prepare the transport for a new media session.
     *
     * Application should call #pjmedia_transport_media_create() instead of 
     * calling this function directly.
     */
    pj_status_t (*media_create)(pjmedia_transport *tp,
				pj_pool_t *sdp_pool,
				unsigned options,
				const pjmedia_sdp_session *remote_sdp,
				unsigned media_index);
 
    /**
     * This function is called by application to start the transport
     * based on local and remote SDP.
     *
     * Application should call #pjmedia_transport_media_start() instead of 
     * calling this function directly.
     */
    pj_status_t (*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);
 
    /**
     * This function is called by application to stop the transport.
     *
     * Application should call #pjmedia_transport_media_stop() instead of 
     * calling this function directly.
     */
    pj_status_t (*media_stop)  (pjmedia_transport *tp);
 
 
    /**
     * This function can be called to destroy this transport.
     *
     * Application should call #pjmedia_transport_close() instead of 
     * calling this function directly.
     */
    pj_status_t (*destroy)(pjmedia_transport *tp);
 
    /**
     * This function is called by the stream when the transport is about
     * to be used by the stream for the first time, and it tells the transport
     * about remote RTP address to send the packet and some callbacks to be
     * called for incoming packets.
     *
     * Application should call #pjmedia_transport_attach2() instead of
     * calling this function directly.
     */
    pj_status_t (*attach2)(pjmedia_transport *tp,
			   pjmedia_transport_attach_param *att_param);
};

这里主要看attach2,这个函数传入rtp和rtcp的回调函数指针,当从网络收到数据时,会通过该回调通知。

创建udp媒体传输

在simpleua.c初始化时,创建完媒体端点pjmedia_endpt后,还会预先创建好udp媒体传输

    /* 
     * 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.
     */
    for (i = 0; i < PJ_ARRAY_SIZE(g_med_transport); ++i) {
      status = pjmedia_transport_udp_create3(g_med_endpt, AF, NULL, NULL, 
                     RTP_PORT + i*2, 0, 
                     &g_med_transport[i]);

pjmedia_transport_udp_create最终调用pjmedia_transport_udp_create3,这个函数先创建rtp和rtcp两个socket,然后调用pjmedia_transport_udp_attach。

调用流:

pjmedia_transport_udp_create3、pjmedia_transport_udp_attach、pj_ioqueue_register_sock2

pjmedia_transport_udp_create3

/**
 * Create UDP stream transport.
 */
PJ_DEF(pj_status_t) pjmedia_transport_udp_create3(pjmedia_endpt *endpt,
						  int af,
						  const char *name,
						  const pj_str_t *addr,
						  int port,
						  unsigned options,
						  pjmedia_transport **p_tp)
{
    pjmedia_sock_info si;
    pj_status_t status;
 
    
    /* Sanity check */
    PJ_ASSERT_RETURN(endpt && port && p_tp, PJ_EINVAL);
 
 
    pj_bzero(&si, sizeof(pjmedia_sock_info));
    si.rtp_sock = si.rtcp_sock = PJ_INVALID_SOCKET;
 
    /* Create RTP socket */
    status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &si.rtp_sock);
    if (status != PJ_SUCCESS)
			goto on_error;
 
    /* Bind RTP socket */
    status = pj_sockaddr_init(af, &si.rtp_addr_name, addr, (pj_uint16_t)port);
    if (status != PJ_SUCCESS)
			goto on_error;
 
    status = pj_sock_bind(si.rtp_sock, &si.rtp_addr_name, 
			  pj_sockaddr_get_len(&si.rtp_addr_name));
    if (status != PJ_SUCCESS)
			goto on_error;
 
 
    /* Create RTCP socket */
    status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &si.rtcp_sock);
    if (status != PJ_SUCCESS)
			goto on_error;
 
    /* Bind RTCP socket */
    status = pj_sockaddr_init(af, &si.rtcp_addr_name, addr, 
			      (pj_uint16_t)(port+1));
    if (status != PJ_SUCCESS)
			goto on_error;
 
    status = pj_sock_bind(si.rtcp_sock, &si.rtcp_addr_name,
			  pj_sockaddr_get_len(&si.rtcp_addr_name));
    if (status != PJ_SUCCESS)
			goto on_error;
 
    
    /* Create UDP transport by attaching socket info */
    return pjmedia_transport_udp_attach( endpt, name, &si, options, p_tp);
 
 
on_error:
    if (si.rtp_sock != PJ_INVALID_SOCKET)
			pj_sock_close(si.rtp_sock);
    if (si.rtcp_sock != PJ_INVALID_SOCKET)
			pj_sock_close(si.rtcp_sock);
    return status;
}

Create & Bind RTP & RTCP socket、Create UDP transport by attaching socket info

transport_udp

struct transport_udp
{
    pjmedia_transport   base;           /**< Base transport.                */

    pj_pool_t          *pool;           /**< Memory pool                    */
    unsigned            options;        /**< Transport options.             */
    unsigned            media_options;  /**< Transport media options.       */
    void               *user_data;      /**< Only valid when attached       */
    //pj_bool_t         attached;       /**< Has attachment?                */
    pj_bool_t           started;        /**< Has started?                   */
    pj_sockaddr         rem_rtp_addr;   /**< Remote RTP address             */
    pj_sockaddr         rem_rtcp_addr;  /**< Remote RTCP address            */
    int                 addr_len;       /**< Length of addresses.           */
    void  (*rtp_cb)(    void*,          /**< To report incoming RTP.        */
                        void*,
                        pj_ssize_t);
    void  (*rtp_cb2)(pjmedia_tp_cb_param*); /**< To report incoming RTP.    */
    void  (*rtcp_cb)(   void*,          /**< To report incoming RTCP.       */
                        void*,
                        pj_ssize_t);

    unsigned            tx_drop_pct;    /**< Percent of tx pkts to drop.    */
    unsigned            rx_drop_pct;    /**< Percent of rx pkts to drop.    */
    pj_ioqueue_t        *ioqueue;       /**< Ioqueue instance.              */

    pj_sock_t           rtp_sock;       /**< RTP socket                     */
    pj_sockaddr         rtp_addr_name;  /**< Published RTP address.         */
    pj_ioqueue_key_t   *rtp_key;        /**< RTP socket key in ioqueue      */
    pj_ioqueue_op_key_t rtp_read_op;    /**< Pending read operation         */
    unsigned            rtp_write_op_id;/**< Next write_op to use           */
    pending_write       rtp_pending_write[MAX_PENDING];  /**< Pending write */
    pj_sockaddr         rtp_src_addr;   /**< Actual packet src addr.        */
    int                 rtp_addrlen;    /**< Address length.                */
    char                rtp_pkt[RTP_LEN];/**< Incoming RTP packet buffer    */

    pj_bool_t           enable_rtcp_mux;/**< Enable RTP & RTCP multiplexing?*/
    pj_bool_t           use_rtcp_mux;   /**< Use RTP & RTCP multiplexing?   */
    pj_sock_t           rtcp_sock;      /**< RTCP socket                    */
    pj_sockaddr         rtcp_addr_name; /**< Published RTCP address.        */
    pj_sockaddr         rtcp_src_addr;  /**< Actual source RTCP address.    */
    unsigned            rtcp_src_cnt;   /**< How many pkt from this addr.   */
    int                 rtcp_addr_len;  /**< Length of RTCP src address.    */
    pj_ioqueue_key_t   *rtcp_key;       /**< RTCP socket key in ioqueue     */
    pj_ioqueue_op_key_t rtcp_read_op;   /**< Pending read operation         */
    pj_ioqueue_op_key_t rtcp_write_op;  /**< Pending write operation        */
    char                rtcp_pkt[RTCP_LEN];/**< Incoming RTCP packet buffer */
};

pjmedia_transport_udp_attach

pjmedia_transport_udp_attach

/**
 * Create UDP stream transport from existing socket info.
 */
PJ_DEF(pj_status_t) pjmedia_transport_udp_attach( pjmedia_endpt *endpt,
                                                  const char *name,
                                                  const pjmedia_sock_info *si,
                                                  unsigned options,
                                                  pjmedia_transport **p_tp)
{
    struct transport_udp *tp;
    pj_pool_t *pool;
    pj_ioqueue_t *ioqueue;
    pj_ioqueue_callback rtp_cb, rtcp_cb;
    pj_grp_lock_t *grp_lock;
    pj_status_t status;


    /* Sanity check */
    PJ_ASSERT_RETURN(endpt && si && p_tp, PJ_EINVAL);

    /* Get ioqueue instance */
    ioqueue = pjmedia_endpt_get_ioqueue(endpt);

    if (name==NULL)
        name = "udp%p";

    /* Create transport structure */
    pool = pjmedia_endpt_create_pool(endpt, name, 512, 512);
    if (!pool)
        return PJ_ENOMEM;
		
    tp = PJ_POOL_ZALLOC_T(pool, struct transport_udp);
    tp->pool = pool;
    tp->options = options;
    pj_memcpy(tp->base.name, pool->obj_name, PJ_MAX_OBJ_NAME);
    tp->base.op = &transport_udp_op;
    tp->base.type = PJMEDIA_TRANSPORT_TYPE_UDP;

    /* Copy socket infos */
    tp->rtp_sock = si->rtp_sock;
    tp->rtp_addr_name = si->rtp_addr_name;
    tp->rtcp_sock = si->rtcp_sock;
    tp->rtcp_addr_name = si->rtcp_addr_name;

    /* If address is 0.0.0.0, use host's IP address */
    if (!pj_sockaddr_has_addr(&tp->rtp_addr_name)) {
        pj_sockaddr hostip;

        status = pj_gethostip(tp->rtp_addr_name.addr.sa_family, &hostip);
        if (status != PJ_SUCCESS)
            goto on_error;

        pj_memcpy(pj_sockaddr_get_addr(&tp->rtp_addr_name), 
                  pj_sockaddr_get_addr(&hostip),
                  pj_sockaddr_get_addr_len(&hostip));
    }

    /* Same with RTCP */
    if (!pj_sockaddr_has_addr(&tp->rtcp_addr_name)) {
        pj_memcpy(pj_sockaddr_get_addr(&tp->rtcp_addr_name),
                  pj_sockaddr_get_addr(&tp->rtp_addr_name),
                  pj_sockaddr_get_addr_len(&tp->rtp_addr_name));
    }

    /* Create group lock */
    status = pj_grp_lock_create(pool, NULL, &grp_lock);
    if (status != PJ_SUCCESS)
        goto on_error;

    pj_grp_lock_add_ref(grp_lock);
    tp->base.grp_lock = grp_lock;

    /* Setup RTP socket with the ioqueue */
    pj_bzero(&rtp_cb, sizeof(rtp_cb));
    rtp_cb.on_read_complete = &on_rx_rtp;
    rtp_cb.on_write_complete = &on_rtp_data_sent;

    status = pj_ioqueue_register_sock2(pool, ioqueue, tp->rtp_sock, grp_lock,
                                       tp, &rtp_cb, &tp->rtp_key);
    if (status != PJ_SUCCESS)
        goto on_error;
    
    /* Disallow concurrency so that detach() and destroy() are
     * synchronized with the callback.
     *
     * Note that we still need this even after group lock is added to
     * maintain the above behavior.
     */
    status = pj_ioqueue_set_concurrency(tp->rtp_key, PJ_FALSE);
    if (status != PJ_SUCCESS)
        goto on_error;
        
    /* Setup RTCP socket with ioqueue */
    pj_bzero(&rtcp_cb, sizeof(rtcp_cb));
    rtcp_cb.on_read_complete = &on_rx_rtcp;

    status = pj_ioqueue_register_sock2(pool, ioqueue, tp->rtcp_sock, grp_lock,
                                       tp, &rtcp_cb, &tp->rtcp_key);
    if (status != PJ_SUCCESS)
        goto on_error;

    status = pj_ioqueue_set_concurrency(tp->rtcp_key, PJ_FALSE);
    if (status != PJ_SUCCESS)
        goto on_error;

    tp->ioqueue = ioqueue;

    /* Done */
    *p_tp = &tp->base;
    return PJ_SUCCESS;


on_error:
    transport_destroy(&tp->base);
    return status;
}


创建transport_udp PJ_POOL_ZALLOC_T,Copy socket infos 到transport_udp、 Setup RTP/RTCP socket with the ioqueue(设置两个回调函数on_rx_rtp、on_rtp_data_sent、on_rx_rtcp)。

udp_attach先申请UDP媒体传输结构体transport_udp *tp的内存,注意,此结构体包含了媒体传输pjmedia_transport和一些回调,但是这些回调还没有设置。其中的操作集指向transport_udp_op。接着把socket注册到媒体端点中的io队列,io队列的读完成回调是on_rx_rtp。从这里可以知道,从网络读到数据时,会调用transport_udp.c中的on_rx_rtp,而在这个回调里,会再调用transport_udp中的回调rtp_cb和rtp_cb2,而这两个回调,创建的时候还没有设置,要等到调用操作集的attach才会设置。

注意,这里有两个attach的地方,一个是创建的时候,调用pjmedia_transport_udp_attach,这个attach会把socket注册到ioqueue,同时ioqueue的读完成回调为transport_udp.c中的on_rx_rtp。

pj_ioqueue_register_sock2

注册一个套接字到I/O队列框架。当一个套接字注册到IO队列时,它可以被修改为使用非阻塞IO。如果被修改了,就不能保证在套接字取消注册后会恢复这种修改。

  • pool – To allocate the resource for the specified handle, which must be valid until the handle/key is unregistered from I/O Queue.
  • ioque – The I/O Queue.
  • sock – The socket.
  • user_data – User data to be associated with the key, which can be retrieved later.
  • cb – Callback to be called when I/O opertion completes.
  • key – Pointer to receive the key to be associated with this socket. Subsequent I/O queue operation will need this key.

第二个attach是操作集的attach,看看上面提到的操作集

static pjmedia_transport_op transport_udp_op = 
{
    &transport_get_info,
    &transport_attach,
    &transport_detach,
    &transport_send_rtp,
    &transport_send_rtcp,
    &transport_send_rtcp2,
    &transport_media_create,
    &transport_encode_sdp,
    &transport_media_start,
    &transport_media_stop,
    &transport_simulate_lost,
    &transport_destroy,
    &transport_attach2
};

第二个attach是transport_attach/transport_attach2,这个attach会再传入rtp_cb和rtcp_cb,而这两个回调,会被on_rx_rtp调用,所以这里有两个回调。总结数据流方向,从网络收到数据,最后会进入attach传入的rtp_cb。但是这个rtp_cb什么时候设置,设置的是谁,这个是在 stream 中实现,下一篇再说。

ioqueue_epoll

pj_ioqueue_t

ioqueue的整体结构pj_ioqueue_t,使用epoll的在ioqueue_epoll.c 下列出ioqueue相关的DECLARE_COMMON_IOQUEUE宏pj_ioqueue_cfgpj_ioqueue_t

#define DECLARE_COMMON_IOQUEUE                      \
    pj_lock_t          *lock;                       \
    pj_bool_t           auto_delete_lock;           \
    pj_ioqueue_cfg      cfg;

/**
 * Additional settings that can be given during ioqueue creation. Application
 * MUST initialize this structure with #pj_ioqueue_cfg_default().
 */
typedef struct pj_ioqueue_cfg
{
    /**
     * Specify flags to control e.g. how events are handled when epoll backend
     * is used on Linux. The values are combination of pj_ioqueue_epoll_flag.
     * The default value is PJ_IOQUEUE_DEFAULT_EPOLL_FLAGS, which by default
     * is set to PJ_IOQUEUE_EPOLL_AUTO. This setting will be ignored for other
     * ioqueue backends.
     */
    unsigned  epoll_flags;

    /**
     * Default concurrency for the handles registered to this ioqueue. Setting
     * this to non-zero enables a handle to process more than one operations
     * at the same time using different threads. Default is
     * PJ_IOQUEUE_DEFAULT_ALLOW_CONCURRENCY. This setting is equivalent to
     * calling pj_ioqueue_set_default_concurrency() after creating the ioqueue.
     */
    pj_bool_t default_concurrency;

} pj_ioqueue_cfg;

/*
 * This describes the I/O queue.
 */
struct pj_ioqueue_t
{
    DECLARE_COMMON_IOQUEUE

    unsigned            max, count;
    //pj_ioqueue_key_t  hlist;
    pj_ioqueue_key_t    active_list;    
    int                 epfd;
    //struct epoll_event *events;
    //struct queue       *queue;

#if PJ_IOQUEUE_HAS_SAFE_UNREG
    pj_mutex_t         *ref_cnt_mutex;
    pj_ioqueue_key_t    closing_list;
    pj_ioqueue_key_t    free_list;
#endif
};

有三个 pj_ioqueue_key_t类型的队列active_list、closing_list、free_list,

pj_ioqueue_key_t

#define DECLARE_COMMON_KEY                          \
    PJ_DECL_LIST_MEMBER(struct pj_ioqueue_key_t);   \
    pj_ioqueue_t           *ioqueue;                \
    pj_grp_lock_t          *grp_lock;               \
    pj_lock_t              *lock;                   \
    pj_bool_t               inside_callback;        \
    pj_bool_t               destroy_requested;      \
    pj_bool_t               allow_concurrent;       \
    pj_sock_t               fd;                     \
    int                     fd_type;                \
    void                   *user_data;              \
    pj_ioqueue_callback     cb;                     \
    int                     connecting;             \
    struct read_operation   read_list;              \
    struct write_operation  write_list;             \
    struct accept_operation accept_list;            \
    UNREG_FIELDS
    
/*
 * This describes each key.
 */
struct pj_ioqueue_key_t
{
    DECLARE_COMMON_KEY
    struct epoll_event ev;
};

pj_ioqueue_key_t中出现了epoll_event 是linux中结构

epoll_event

typedef [union](https://so.csdn.net/so/search?q=union&spm=1001.2101.3001.7020) epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;//保存触发事件的某个文件描述符相关的数据

struct epoll_event {
    __uint32_t events;   /* [epoll](https://so.csdn.net/so/search?q=epoll&spm=1001.2101.3001.7020) event */
    epoll_data_t data;   /* User data variable */
};

pj_ioqueue_callback

接下来介绍I/O结束的回调函数

/**
 * This structure describes the callbacks to be called when I/O operation
 * completes.
 */
typedef struct pj_ioqueue_callback
{
    /**
     * This callback is called when #pj_ioqueue_recv or #pj_ioqueue_recvfrom
     * completes.
     *
     * @param key           The key.
     * @param op_key        Operation key.
     * @param bytes_read    >= 0 to indicate the amount of data read,
     *                      otherwise negative value containing the error
     *                      code. To obtain the pj_status_t error code, use
     *                      (pj_status_t code = -bytes_read).
     */
    void (*on_read_complete)(pj_ioqueue_key_t *key,
                             pj_ioqueue_op_key_t *op_key,
                             pj_ssize_t bytes_read);

    /**
     * This callback is called when #pj_ioqueue_send or #pj_ioqueue_sendto
     * completes.
     *
     * @param key           The key.
     * @param op_key        Operation key.
     * @param bytes_sent    >= 0 to indicate the amount of data written,
     *                      otherwise negative value containing the error
     *                      code. To obtain the pj_status_t error code, use
     *                      (pj_status_t code = -bytes_sent).
     */
    void (*on_write_complete)(pj_ioqueue_key_t *key,
                              pj_ioqueue_op_key_t *op_key,
                              pj_ssize_t bytes_sent);

    /**
     * This callback is called when #pj_ioqueue_accept completes.
     *
     * @param key           The key.
     * @param op_key        Operation key.
     * @param sock          Newly connected socket.
     * @param status        Zero if the operation completes successfully.
     */
    void (*on_accept_complete)(pj_ioqueue_key_t *key,
                               pj_ioqueue_op_key_t *op_key,
                               pj_sock_t sock,
                               pj_status_t status);

    /**
     * This callback is called when #pj_ioqueue_connect completes.
     *
     * @param key           The key.
     * @param status        PJ_SUCCESS if the operation completes successfully.
     */
    void (*on_connect_complete)(pj_ioqueue_key_t *key,
                                pj_status_t status);
} pj_ioqueue_callback;

pj_ioqueue_op_key_t

typedef struct pj_ioqueue_op_key_t
{
    void *internal__[32];           /**< Internal I/O Queue data.   */
    void *activesock_data;          /**< Active socket data.        */
    void *user_data;                /**< Application data.          */
} pj_ioqueue_op_key_t;

初始化相关

pj_ioqueue_create2

pj_ioqueue_create(空实现调用pj_ioqueue_create2)

status = pj_ioqueue_create( endpt->pool, PJSIP_MAX_TRANSPORTS, &endpt->ioqueue);
if (status != PJ_SUCCESS) {
    goto on_error;
}

初始化ioqueue的空间、ioqueue->lock、ioqueue->auto_delete_lock、ioqueue->cfg(pj_ioqueue_cfg)、ioqueue->max、ioqueue->count、ioqueue->cfg.epoll_flags(epoll type)、ioqueue->ref_cnt_mutex、ioqueue->free_list(对max_fd个key初始化key->ref_count、key->lock然后加入freelist)、ioqueue->closing_list、ioqueue->epfd(epoll fd调用epoll create)

ioqueue_init_key

static pj_status_t ioqueue_init_key( pj_pool_t *pool,
                                     pj_ioqueue_t *ioqueue,
                                     pj_ioqueue_key_t *key,
                                     pj_sock_t sock,
                                     pj_grp_lock_t *grp_lock,
                                     void *user_data,
                                     const pj_ioqueue_callback *cb)

初始化key的key->ioqueue、key->fd、key->user_data(这里放到是transport_udp)、key->read_list、key->write_list、key->accept_list、key->connecting = 0、key->cb(callback)、key->closing 、key->allow_concurrent(pj_ioqueue_set_concurrency)、key->fd_type、key->grp_lock

pj_ioqueue_register_sock2

pjmedia_transport_udp_create3->pjmedia_transport_udp_attach->pj_ioqueue_register_sock2

os_ioctl 设置socket to nonblocking.

从ioqueue的freelist中取得key,ioqueue_init_key,然后初始化key->ev(epoll_event类型 data.ptr 是key本身), os_epoll_ctl注册 fd-events监听其socket事件。

PJ_DEF(pj_status_t) pj_ioqueue_register_sock2(pj_pool_t *pool,
                                              pj_ioqueue_t *ioqueue,
                                              pj_sock_t sock,
                                              pj_grp_lock_t *grp_lock,
                                              void *user_data,
                                              const pj_ioqueue_callback *cb,
                                              pj_ioqueue_key_t **p_key)
  
    status = pj_ioqueue_register_sock2(pool, ioqueue, tp->rtp_sock, grp_lock,
                                       tp, &rtp_cb, &tp->rtp_key);

注意在pjmedia_transport_udp_attach 中调用pj_ioqueue_register_sock2传入的是tp->rtp_key,pj_ioqueue_register_sock2返回后,会将绑定socket后的key保存在rtp_key中

ioqueue 读取写入流程

ioqueue 读取全流程

pj_ioqueue_poll,发现事件-》ioqueue_dispatch_read_event-》on_rx_request-》on_rx_request

pj_ioqueue_poll

事件触发后执行主要使用os_epoll_wait,执行分发回调

一般会另开一个工作线程,不停循环,执行epoll_wait以监听读取/写入rtp包的事件请求

while(1){
		pj_ioqueue_poll()
}

下面详细介绍pj_ioqueue_poll

先调用os_epoll_wait,事件放到events中,遍历所有events。根据read,write事件放入到queue中,queue结构如下:

struct queue
{
    pj_ioqueue_key_t        *key;
    enum ioqueue_event_type  event_type;
};

enum ioqueue_event_type
{
    NO_EVENT,
    READABLE_EVENT  = 1,
    WRITEABLE_EVENT = 2,
    EXCEPTION_EVENT = 4,
};

最后在遍历queue,根据事件执行相应的处理函数

READABLE_EVENT:ioqueue_dispatch_read_event

WRITEABLE_EVENT:ioqueue_dispatch_write_event

EXCEPTION_EVENT:ioqueue_dispatch_exception_e

ioqueue_dispatch_read_event 读取操作

首先要看一下read_operation

struct read_operation
{
    PJ_DECL_LIST_MEMBER(struct read_operation);
    pj_ioqueue_operation_e  op;

    void                   *buf;
    pj_size_t               size;
    unsigned                flags;
    pj_sockaddr_t          *rmt_addr;
    int                    *rmt_addrlen;
};

/**
 * Types of pending I/O Queue operation. This enumeration is only used
 * internally within the ioqueue.
 */
typedef enum pj_ioqueue_operation_e
{
    PJ_IOQUEUE_OP_NONE          = 0,    /**< No operation.          */
    PJ_IOQUEUE_OP_READ          = 1,    /**< read() operation.      */
    PJ_IOQUEUE_OP_RECV          = 2,    /**< recv() operation.      */
    PJ_IOQUEUE_OP_RECV_FROM     = 4,    /**< recvfrom() operation.  */
    PJ_IOQUEUE_OP_WRITE         = 8,    /**< write() operation.     */
    PJ_IOQUEUE_OP_SEND          = 16,   /**< send() operation.      */
    PJ_IOQUEUE_OP_SEND_TO       = 32,   /**< sendto() operation.    */
#if defined(PJ_HAS_TCP) && PJ_HAS_TCP != 0
    PJ_IOQUEUE_OP_ACCEPT        = 64,   /**< accept() operation.    */
    PJ_IOQUEUE_OP_CONNECT       = 128   /**< connect() operation.   */
#endif  /* PJ_HAS_TCP */
} pj_ioqueue_operation_e;

众多read_operation会挂在key中struct read_operation read_list; 上通过key_has_pending_read 判断是否有pending的read操作

具体流程:

先看key read_list上有没有pending_read,有的话,从read_list取出,根据read_list的read_op确定读入大小,pj_sock_recvfrom接受数据的函数,将数据读入到read_op中,最后调用on_read_complete,回调函数已在pj_ioqueue_register_sock2时设置过,传入read_op

(*h->cb.on_read_complete)(h, 
                                      (pj_ioqueue_op_key_t*)read_op,
                                      bytes_read);

ioqueue_dispatch_write_event写操作

与ioqueue_dispatch_read_event相似,先看write_operation

struct write_operation
{
    PJ_DECL_LIST_MEMBER(struct write_operation);
    pj_ioqueue_operation_e  op;

    char                   *buf;
    pj_size_t               size;
    pj_ssize_t              written;
    unsigned                flags;
    pj_sockaddr_in          rmt_addr;
    int                     rmt_addrlen;
};

众多write_operation会挂在key中struct write_operation write_list; 上通过key_has_pending_write 判断是否有pending的write操作

具体流程:

先看key write_list上有没有pending_write,有的话,从write_list取出,根据write_list的 write_op确定写大小,要写入的数据,将数据写入调用pj_sock_send函数Transmit data to the socket.,最后调用on_write_complete,回调函数已在pj_ioqueue_register_sock2时设置过,传入write_op

if (h->cb.on_write_complete && !IS_CLOSING(h)) {
                (*h->cb.on_write_complete)(h, 
                                           (pj_ioqueue_op_key_t*)write_op,
                                           write_op->written);
            }

on_rx_rtp 读取操作

on_rx_rtp是被下面调用

(*h->cb.on_read_complete)(h, 
                                      (pj_ioqueue_op_key_t*)read_op,
                                      bytes_read);

这里有一个很有意思的问题,就是read_op 在on_rx_rtp中竟然没有使用,下面来分析一下原因

udp = (struct transport_udp*) pj_ioqueue_get_user_data(key);

我们的read_op是从readlist中取出的,readlist对于读操作添加是依靠pj_ioqueue_recvfrom函数,在data is not immediately available时将read_op加入readlist

read_op->op = PJ_IOQUEUE_OP_RECV_FROM;
read_op->buf = buffer;
read_op->size = *length;
read_op->flags = flags;
read_op->rmt_addr = addr;
read_op->rmt_addrlen = addrlen;

pj_ioqueue_lock_key(key);
/* Check again. Handle may have been closed after the previous check
 * in multithreaded app. If we add bad handle to the set it will
 * corrupt the ioqueue set. See #913
 */
if (IS_CLOSING(key)) {
    pj_ioqueue_unlock_key(key);
    return PJ_ECANCELLED;
}
pj_list_insert_before(&key->read_list, read_op);
ioqueue_add_to_set(key->ioqueue, key, READABLE_EVENT);

这里read_op->buf = buffer;的buffer,来自udp->rtp_pkt,相当于直接写入了rtp_pkt,所以不用read_op了。

on_rx_rtp是一个while循环,条件如下status来自pj_ioqueue_recvfrom的结果

status != PJ_EPENDING && status != PJ_ECANCELLED &&
             udp->started

call_rtp_cb

在while循环里,先执行call_rtp_cb,设置pjmedia_tp_cb_param param;,调用(*cb2)(&param);cb2由transport_attach2-》tp_attach设置为stream.c ::on_rx_rtp。注意param.pkt = udp->rtp_pkt;,这里rtp_pkt其实就是ioqueue_dispatch_read_event中read_op->buf中读到的数据rtp包

/* Call RTP cb. */
static void call_rtp_cb(struct transport_udp *udp, pj_ssize_t bytes_read, 
                        pj_bool_t *rem_switch)
{
    void (*cb)(void*,void*,pj_ssize_t);
    void (*cb2)(pjmedia_tp_cb_param*);
    void *user_data;

    cb = udp->rtp_cb;
    cb2 = udp->rtp_cb2;
    user_data = udp->user_data;

    if (cb2) {
        pjmedia_tp_cb_param param;

        param.user_data = user_data;
        param.pkt = udp->rtp_pkt;
        param.size = bytes_read;
        param.src_addr = &udp->rtp_src_addr;
        param.rem_switch = PJ_FALSE;
        (*cb2)(&param);
        if (rem_switch)
            *rem_switch = param.rem_switch;
    } else if (cb) {
        (*cb)(user_data, udp->rtp_pkt, bytes_read);
    }
}

param.user_data = user_data; 注意这个user_data,是pjmedia_stream *stream

pj_ioqueue_recvfrom

接下来是调用pj_ioqueue_recvfrom,至于为什么明明ioqueue_dispatch_read_event已经读取了数据,此时还在读取数据,是因为可能有新的rtp包到达,pj_ioqueue_recvfrom查看有没有到达的包,如果有就调用pj_sock_recvfrom继续读读到udp->rtp_pkt,如果没有加到readlist中,返回PJ_EPENDING,结束on_rx_rtp中的while循环。

on_rx_rtp::cb2

Stream.c中的回调 tp_attach中设置该回调

该函数处理接收到的rtp包, 解析成payload和head

Put "good" packet to jitter buffer,需要先把payload解析成frame,再把frame放入jitter buffer

epoll学习

  1. struct epoll_event

结构体epoll_event被用于注册所感兴趣的事件和回传所发生待处理的事件,定义如下:

typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t;//保存触发事件的某个文件描述符相关的数据

struct epoll_event { __uint32_t events; /* epoll event / epoll_data_t data; / User data variable */ };

其中events表示感兴趣的事件和被触发的事件,可能的取值为: EPOLLIN:表示对应的文件描述符可以读; EPOLLOUT:表示对应的文件描述符可以写; EPOLLPRI:表示对应的文件描述符有紧急的数可读;

EPOLLERR:表示对应的文件描述符发生错误; EPOLLHUP:表示对应的文件描述符被挂断; EPOLLET: ET的epoll工作模式;

所涉及到的函数有:

1、epoll_create函数 函数声明:int epoll_create(int size)

功能:该函数生成一个epoll专用的文件描述符,其中的参数是指定生成描述符的最大范围;

2、epoll_ctl函数 函数声明:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) 在epfd上注册一个fd-events, 监听event->events的事件类型

功能:用于控制某个文件描述符上的事件,可以注册事件,修改事件,删除事件。

@epfd:由epoll_create生成的epoll专用的文件描述符;

@op:要进行的操作,EPOLL_CTL_ADD注册、EPOLL_CTL_MOD修改、EPOLL_CTL_DEL删除;

@fd:关联的文件描述符;

@event:指向epoll_event的指针;

成功:0;失败:-1

3、epoll_wait函数 函数声明:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)

功能:该函数用于轮询I/O事件的发生;

@epfd:由epoll_create生成的epoll专用的文件描述符;

@epoll_event:用于回传代处理事件的数组;

@maxevents:每次能处理的事件数;

@timeout:等待I/O事件发生的超时值;

成功:返回发生的事件数;失败:-1

epoll_ctl 是用于控制 epoll 实例的系统调用之一,它用于向 epoll 实例注册或删除事件,并指定相应的文件描述符和事件类型。

epoll_ctl 的原型如下:

cCopy code
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

其中参数的含义如下:

  • epfd:epoll 实例的文件描述符,通过 epoll_create 创建得到。

  • op
    

    :操作类型,可以是以下几种值之一:

    • EPOLL_CTL_ADD:添加一个新的文件描述符到 epoll 实例中。
    • EPOLL_CTL_MOD:修改一个已注册的文件描述符的事件类型。
    • EPOLL_CTL_DEL:从 epoll 实例中删除一个文件描述符。
  • fd:要添加、修改或删除的文件描述符。

  • event:一个指向 struct epoll_event 结构体的指针,用于描述要注册的事件类型和相关的数据。

struct epoll_event 结构体定义如下:

cCopy code


解释struct epoll_event {
    uint32_t events;  // 事件类型,可以是 EPOLLIN、EPOLLOUT、EPOLLERR 等
    epoll_data_t data; // 事件关联的数据,通常是一个联合体
};

epoll_ctl 的主要作用是管理 epoll 实例中的文件描述符和事件,可以添加、修改或删除特定的文件描述符以及相应的事件类型。通过这个系统调用,可以实现高效的 I/O 多路复用机制,使得应用程序能够监视多个文件描述符上的事件,并在事件发生时做出相应的处理。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>

#define MAX_EVENTS 10

int main() {
    int epoll_fd, nfds, i;
    struct epoll_event event, events[MAX_EVENTS];

    // 创建 epoll 实例
    epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("epoll_create1");
        exit(EXIT_FAILURE);
    }

    // 打开一个文件描述符(这里以标准输入描述符为例)
    int stdin_fd = STDIN_FILENO;

    // 配置要监听的事件
    event.events = EPOLLIN; // 监听读事件
    event.data.fd = stdin_fd; // 将标准输入描述符与事件关联

    // 将事件添加到 epoll 实例中
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, stdin_fd, &event) == -1) {
        perror("epoll_ctl: EPOLL_CTL_ADD");
        close(epoll_fd);
        exit(EXIT_FAILURE);
    }

    // 等待事件发生
    while (1) {
        nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (nfds == -1) {
            perror("epoll_wait");
            close(epoll_fd);
            exit(EXIT_FAILURE);
        }

        // 处理所有发生的事件
        for (i = 0; i < nfds; ++i) {
            if (events[i].events & EPOLLIN) {
                printf("Event on file descriptor %d: EPOLLIN\n", events[i].data.fd);

                // 从标准输入读取数据并进行处理
                char buffer[1024];
                ssize_t bytes_read = read(events[i].data.fd, buffer, sizeof(buffer));
                if (bytes_read == -1) {
                    perror("read");
                    close(epoll_fd);
                    exit(EXIT_FAILURE);
                }
                // 处理读取的数据
                // ...
            }
        }
    }

    // 关闭 epoll 实例
    close(epoll_fd);

    return 0;
}

Transport

媒体传输封装了网络收发细节,pjmedia_transport可以是udp、srtp、ice等,这里以udp为例。

相关结构体

结构体pjmedia_transport

/**
 * This structure declares media transport. A media transport is called
 * by the stream to transmit a packet, and will notify stream when
 * incoming packet is arrived.
 */
struct pjmedia_transport
{
    /** Transport name (for logging purpose). */
    char		     name[PJ_MAX_OBJ_NAME];
 
    /** Transport type. */
    pjmedia_transport_type   type;
 
    /** Transport's "virtual" function table. */
    pjmedia_transport_op    *op;
 
    /** Application/user data */
    void		    *user_data;
};

type:传输类型,上面讲过,这里以udp为例。

op:操作集,每种传输类型实现了同一组接口。

user_data:应用层用户数据

pjmedia_transport_op

操作集是核心,这里列举重要的一些函数。pjmedia_transport_op

/**
 * This structure describes the operations for the stream transport.
 */
struct pjmedia_transport_op
{
 
    /**
     * This function is called by the stream when the transport is about
     * to be used by the stream for the first time, and it tells the transport
     * about remote RTP address to send the packet and some callbacks to be 
     * called for incoming packets. This function exists for backwards
     * compatibility. Transports should implement attach2 instead.
     *
     * Application should call #pjmedia_transport_attach() instead of 
     * calling this function directly.
     */
    pj_status_t (*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 size),
			  void (*rtcp_cb)(void *user_data,
					  void *pkt,
					  pj_ssize_t size));
 
    /**
     * This function is called by the stream to send RTP packet using the 
     * transport.
     *
     * Application should call #pjmedia_transport_send_rtp() instead of 
     * calling this function directly.
     */
    pj_status_t (*send_rtp)(pjmedia_transport *tp,
			    const void *pkt,
			    pj_size_t size);
 
    /**
     * Prepare the transport for a new media session.
     *
     * Application should call #pjmedia_transport_media_create() instead of 
     * calling this function directly.
     */
    pj_status_t (*media_create)(pjmedia_transport *tp,
				pj_pool_t *sdp_pool,
				unsigned options,
				const pjmedia_sdp_session *remote_sdp,
				unsigned media_index);
 
    /**
     * This function is called by application to start the transport
     * based on local and remote SDP.
     *
     * Application should call #pjmedia_transport_media_start() instead of 
     * calling this function directly.
     */
    pj_status_t (*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);
 
    /**
     * This function is called by application to stop the transport.
     *
     * Application should call #pjmedia_transport_media_stop() instead of 
     * calling this function directly.
     */
    pj_status_t (*media_stop)  (pjmedia_transport *tp);
 
 
    /**
     * This function can be called to destroy this transport.
     *
     * Application should call #pjmedia_transport_close() instead of 
     * calling this function directly.
     */
    pj_status_t (*destroy)(pjmedia_transport *tp);
 
    /**
     * This function is called by the stream when the transport is about
     * to be used by the stream for the first time, and it tells the transport
     * about remote RTP address to send the packet and some callbacks to be
     * called for incoming packets.
     *
     * Application should call #pjmedia_transport_attach2() instead of
     * calling this function directly.
     */
    pj_status_t (*attach2)(pjmedia_transport *tp,
			   pjmedia_transport_attach_param *att_param);
};

op的默认初始化方法

static pjmedia_transport_op transport_udp_op = 
{
    &transport_get_info,
    &transport_attach,
    &transport_detach,
    &transport_send_rtp,
    &transport_send_rtcp,
    &transport_send_rtcp2,
    &transport_media_create,
    &transport_encode_sdp,
    &transport_media_start,
    &transport_media_stop,
    &transport_simulate_lost,
    &transport_destroy,
    &transport_attach2
};

这里主要看attach2,这个函数传入rtp和rtcp的回调函数指针,当从网络收到数据时,会通过该回调通知。

pjmedia_transport_attach_param

/**
 * This structure describes the data passed when calling
 * #pjmedia_transport_attach2().
 */
struct pjmedia_transport_attach_param
{
    /**
     * The media stream.
     */
    void *stream;

    /**
     * Indicate the stream type, either it's audio (PJMEDIA_TYPE_AUDIO) 
     * or video (PJMEDIA_TYPE_VIDEO).
     */
    pjmedia_type media_type;

    /**
     * Remote RTP address to send RTP packet to.
     */
    pj_sockaddr rem_addr;

    /**
     * Optional remote RTCP address. If the argument is NULL
     * or if the address is zero, the RTCP address will be
     * calculated from the RTP address (which is RTP port plus one).
     */
    pj_sockaddr rem_rtcp;

    /**
     * Length of the remote address.
     */
    unsigned addr_len;

    /**
     * Arbitrary user data to be set when the callbacks are called.
     */
    void *user_data;

    /**
     * Callback to be called when RTP packet is received on the transport.
     */
    void (*rtp_cb)(void *user_data, void *pkt, pj_ssize_t);

    /**
     * Callback to be called when RTCP packet is received on the transport.
     */
    void (*rtcp_cb)(void *user_data, void *pkt, pj_ssize_t);

    /**
     * Callback to be called when RTP packet is received on the transport.
     */
    void (*rtp_cb2)(pjmedia_tp_cb_param *param);

};

transport_udp

struct transport_udp
{
    pjmedia_transport   base;           /**< Base transport.                */

    pj_pool_t          *pool;           /**< Memory pool                    */
    unsigned            options;        /**< Transport options.             */
    unsigned            media_options;  /**< Transport media options.       */
    void               *user_data;      /**< Only valid when attached       */
    //pj_bool_t         attached;       /**< Has attachment?                */
    pj_bool_t           started;        /**< Has started?                   */
    pj_sockaddr         rem_rtp_addr;   /**< Remote RTP address             */
    pj_sockaddr         rem_rtcp_addr;  /**< Remote RTCP address            */
    int                 addr_len;       /**< Length of addresses.           */
    void  (*rtp_cb)(    void*,          /**< To report incoming RTP.        */
                        void*,
                        pj_ssize_t);
    void  (*rtp_cb2)(pjmedia_tp_cb_param*); /**< To report incoming RTP.    */
    void  (*rtcp_cb)(   void*,          /**< To report incoming RTCP.       */
                        void*,
                        pj_ssize_t);

    unsigned            tx_drop_pct;    /**< Percent of tx pkts to drop.    */
    unsigned            rx_drop_pct;    /**< Percent of rx pkts to drop.    */
    pj_ioqueue_t        *ioqueue;       /**< Ioqueue instance.              */

    pj_sock_t           rtp_sock;       /**< RTP socket                     */
    pj_sockaddr         rtp_addr_name;  /**< Published RTP address.         */
    pj_ioqueue_key_t   *rtp_key;        /**< RTP socket key in ioqueue      */
    pj_ioqueue_op_key_t rtp_read_op;    /**< Pending read operation         */
    unsigned            rtp_write_op_id;/**< Next write_op to use           */
    pending_write       rtp_pending_write[MAX_PENDING];  /**< Pending write */
    pj_sockaddr         rtp_src_addr;   /**< Actual packet src addr.        */
    int                 rtp_addrlen;    /**< Address length.                */
    char                rtp_pkt[RTP_LEN];/**< Incoming RTP packet buffer    */

    pj_bool_t           enable_rtcp_mux;/**< Enable RTP & RTCP multiplexing?*/
    pj_bool_t           use_rtcp_mux;   /**< Use RTP & RTCP multiplexing?   */
    pj_sock_t           rtcp_sock;      /**< RTCP socket                    */
    pj_sockaddr         rtcp_addr_name; /**< Published RTCP address.        */
    pj_sockaddr         rtcp_src_addr;  /**< Actual source RTCP address.    */
    unsigned            rtcp_src_cnt;   /**< How many pkt from this addr.   */
    int                 rtcp_addr_len;  /**< Length of RTCP src address.    */
    pj_ioqueue_key_t   *rtcp_key;       /**< RTCP socket key in ioqueue     */
    pj_ioqueue_op_key_t rtcp_read_op;   /**< Pending read operation         */
    pj_ioqueue_op_key_t rtcp_write_op;  /**< Pending write operation        */
    char                rtcp_pkt[RTCP_LEN];/**< Incoming RTCP packet buffer */
};

pj_ioqueue_callback

/**
 * This structure describes the callbacks to be called when I/O operation
 * completes.
 */
typedef struct pj_ioqueue_callback
{
    /**
     * This callback is called when #pj_ioqueue_recv or #pj_ioqueue_recvfrom
     * completes.
     *
     * @param key           The key.
     * @param op_key        Operation key.
     * @param bytes_read    >= 0 to indicate the amount of data read,
     *                      otherwise negative value containing the error
     *                      code. To obtain the pj_status_t error code, use
     *                      (pj_status_t code = -bytes_read).
     */
    void (*on_read_complete)(pj_ioqueue_key_t *key,
                             pj_ioqueue_op_key_t *op_key,
                             pj_ssize_t bytes_read);

    /**
     * This callback is called when #pj_ioqueue_send or #pj_ioqueue_sendto
     * completes.
     *
     * @param key           The key.
     * @param op_key        Operation key.
     * @param bytes_sent    >= 0 to indicate the amount of data written,
     *                      otherwise negative value containing the error
     *                      code. To obtain the pj_status_t error code, use
     *                      (pj_status_t code = -bytes_sent).
     */
    void (*on_write_complete)(pj_ioqueue_key_t *key,
                              pj_ioqueue_op_key_t *op_key,
                              pj_ssize_t bytes_sent);

    /**
     * This callback is called when #pj_ioqueue_accept completes.
     *
     * @param key           The key.
     * @param op_key        Operation key.
     * @param sock          Newly connected socket.
     * @param status        Zero if the operation completes successfully.
     */
    void (*on_accept_complete)(pj_ioqueue_key_t *key,
                               pj_ioqueue_op_key_t *op_key,
                               pj_sock_t sock,
                               pj_status_t status);

    /**
     * This callback is called when #pj_ioqueue_connect completes.
     *
     * @param key           The key.
     * @param status        PJ_SUCCESS if the operation completes successfully.
     */
    void (*on_connect_complete)(pj_ioqueue_key_t *key,
                                pj_status_t status);
} pj_ioqueue_callback;

创建udp media transport

在simpleua.c初始化时,创建完媒体端点pjmedia_endpt后,还会预先创建好udp媒体传输在main.c在sdp和invite session之前

    /* 
     * 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.
     */
    for (i = 0; i < PJ_ARRAY_SIZE(g_med_transport); ++i) {
      status = pjmedia_transport_udp_create3(g_med_endpt, AF, NULL, NULL, 
                     RTP_PORT + i*2, 0, 
                     &g_med_transport[i]);

pjmedia_transport_udp_create最终调用pjmedia_transport_udp_create3,这个函数先创建rtp和rtcp两个socket,然后调用pjmedia_transport_udp_attach。

transport_udp_create调用流

pjmedia_transport_udp_create3 (Create & Bind RTP & RTCP socket)->pjmedia_transport_udp_attach(创建transport_udp,初始化socket infos,pj_ioqueue_register_sock2:注册socket到 I/O queue同时设置回调函数,以便异步接受消息)

pjmedia_transport_udp_create3
/**
 * Create UDP stream transport.
 */
PJ_DEF(pj_status_t) pjmedia_transport_udp_create3(pjmedia_endpt *endpt,
						  int af,
						  const char *name,
						  const pj_str_t *addr,
						  int port,
						  unsigned options,
						  pjmedia_transport **p_tp)
{
    pjmedia_sock_info si;
    pj_status_t status;
 
    
    /* Sanity check */
    PJ_ASSERT_RETURN(endpt && port && p_tp, PJ_EINVAL);
 
 
    pj_bzero(&si, sizeof(pjmedia_sock_info));
    si.rtp_sock = si.rtcp_sock = PJ_INVALID_SOCKET;
 
    /* Create RTP socket */
    status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &si.rtp_sock);
    if (status != PJ_SUCCESS)
			goto on_error;
 
    /* Bind RTP socket */
    status = pj_sockaddr_init(af, &si.rtp_addr_name, addr, (pj_uint16_t)port);
    if (status != PJ_SUCCESS)
			goto on_error;
 
    status = pj_sock_bind(si.rtp_sock, &si.rtp_addr_name, 
			  pj_sockaddr_get_len(&si.rtp_addr_name));
    if (status != PJ_SUCCESS)
			goto on_error;
 
 
    /* Create RTCP socket */
    status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &si.rtcp_sock);
    if (status != PJ_SUCCESS)
			goto on_error;
 
    /* Bind RTCP socket */
    status = pj_sockaddr_init(af, &si.rtcp_addr_name, addr, 
			      (pj_uint16_t)(port+1));
    if (status != PJ_SUCCESS)
			goto on_error;
 
    status = pj_sock_bind(si.rtcp_sock, &si.rtcp_addr_name,
			  pj_sockaddr_get_len(&si.rtcp_addr_name));
    if (status != PJ_SUCCESS)
			goto on_error;
 
    
    /* Create UDP transport by attaching socket info */
    return pjmedia_transport_udp_attach( endpt, name, &si, options, p_tp);
 
 
on_error:
    if (si.rtp_sock != PJ_INVALID_SOCKET)
			pj_sock_close(si.rtp_sock);
    if (si.rtcp_sock != PJ_INVALID_SOCKET)
			pj_sock_close(si.rtcp_sock);
    return status;
}

Create & Bind RTP & RTCP socket、Create UDP transport by attaching socket info

pjmedia_transport_udp_attach

pjmedia_transport_udp_attach

/**
 * Create UDP stream transport from existing socket info.
 */
PJ_DEF(pj_status_t) pjmedia_transport_udp_attach( pjmedia_endpt *endpt,
                                                  const char *name,
                                                  const pjmedia_sock_info *si,
                                                  unsigned options,
                                                  pjmedia_transport **p_tp)
{
    struct transport_udp *tp;
    pj_pool_t *pool;
    pj_ioqueue_t *ioqueue;
    pj_ioqueue_callback rtp_cb, rtcp_cb;
    pj_grp_lock_t *grp_lock;
    pj_status_t status;


    /* Sanity check */
    PJ_ASSERT_RETURN(endpt && si && p_tp, PJ_EINVAL);

    /* Get ioqueue instance */
    ioqueue = pjmedia_endpt_get_ioqueue(endpt);

    if (name==NULL)
        name = "udp%p";

    /* Create transport structure */
    pool = pjmedia_endpt_create_pool(endpt, name, 512, 512);
    if (!pool)
        return PJ_ENOMEM;
		
    tp = PJ_POOL_ZALLOC_T(pool, struct transport_udp);
    tp->pool = pool;
    tp->options = options;
    pj_memcpy(tp->base.name, pool->obj_name, PJ_MAX_OBJ_NAME);
    tp->base.op = &transport_udp_op;
    tp->base.type = PJMEDIA_TRANSPORT_TYPE_UDP;

    /* Copy socket infos */
    tp->rtp_sock = si->rtp_sock;
    tp->rtp_addr_name = si->rtp_addr_name;
    tp->rtcp_sock = si->rtcp_sock;
    tp->rtcp_addr_name = si->rtcp_addr_name;

    /* If address is 0.0.0.0, use host's IP address */
    if (!pj_sockaddr_has_addr(&tp->rtp_addr_name)) {
        pj_sockaddr hostip;

        status = pj_gethostip(tp->rtp_addr_name.addr.sa_family, &hostip);
        if (status != PJ_SUCCESS)
            goto on_error;

        pj_memcpy(pj_sockaddr_get_addr(&tp->rtp_addr_name), 
                  pj_sockaddr_get_addr(&hostip),
                  pj_sockaddr_get_addr_len(&hostip));
    }

    /* Same with RTCP */
    if (!pj_sockaddr_has_addr(&tp->rtcp_addr_name)) {
        pj_memcpy(pj_sockaddr_get_addr(&tp->rtcp_addr_name),
                  pj_sockaddr_get_addr(&tp->rtp_addr_name),
                  pj_sockaddr_get_addr_len(&tp->rtp_addr_name));
    }

    /* Create group lock */
    status = pj_grp_lock_create(pool, NULL, &grp_lock);
    if (status != PJ_SUCCESS)
        goto on_error;

    pj_grp_lock_add_ref(grp_lock);
    tp->base.grp_lock = grp_lock;

    /* Setup RTP socket with the ioqueue */
    pj_bzero(&rtp_cb, sizeof(rtp_cb));
    rtp_cb.on_read_complete = &on_rx_rtp;
    rtp_cb.on_write_complete = &on_rtp_data_sent;

    status = pj_ioqueue_register_sock2(pool, ioqueue, tp->rtp_sock, grp_lock,
                                       tp, &rtp_cb, &tp->rtp_key);
    if (status != PJ_SUCCESS)
        goto on_error;
    
    /* Disallow concurrency so that detach() and destroy() are
     * synchronized with the callback.
     *
     * Note that we still need this even after group lock is added to
     * maintain the above behavior.
     */
    status = pj_ioqueue_set_concurrency(tp->rtp_key, PJ_FALSE);
    if (status != PJ_SUCCESS)
        goto on_error;
        
    /* Setup RTCP socket with ioqueue */
    pj_bzero(&rtcp_cb, sizeof(rtcp_cb));
    rtcp_cb.on_read_complete = &on_rx_rtcp;

    status = pj_ioqueue_register_sock2(pool, ioqueue, tp->rtcp_sock, grp_lock,
                                       tp, &rtcp_cb, &tp->rtcp_key);
    if (status != PJ_SUCCESS)
        goto on_error;

    status = pj_ioqueue_set_concurrency(tp->rtcp_key, PJ_FALSE);
    if (status != PJ_SUCCESS)
        goto on_error;

    tp->ioqueue = ioqueue;

    /* Done */
    *p_tp = &tp->base;
    return PJ_SUCCESS;


on_error:
    transport_destroy(&tp->base);
    return status;
}


创建transport_udp :PJ_POOL_ZALLOC_T,Copy socket infos 到transport_udp、 Setup RTP/RTCP socket with the ioqueue(设置回调函数on_rx_rtp、on_rtp_data_sent、on_rx_rtcp)。

udp_attach先申请UDP媒体传输结构体transport_udp *tp的内存,注意,此结构体包含了媒体传输pjmedia_transport和一些回调,但是这些回调还没有设置。其中的操作集指向transport_udp_op。接着把socket注册到媒体端点中的io队列,io队列的读完成回调是on_rx_rtp。从这里可以知道,从网络读到数据时,会调用transport_udp.c中的on_rx_rtp,而在这个回调里,会再调用transport_udp中的回调rtp_cb和rtp_cb2,而这两个回调,创建的时候还没有设置,要等到调用操作集的attach才会设置。

注意,这里有两个attach的地方,一个是创建的时候,调用pjmedia_transport_udp_attach,这个attach会把socket注册到ioqueue,同时ioqueue的读完成回调为transport_udp.c中的on_rx_rtp。

pj_ioqueue_register_sock2

注册一个套接字到I/O队列框架。当一个套接字注册到IO队列时,它可以被修改为使用非阻塞IO。如果被修改了,就不能保证在套接字取消注册后会恢复这种修改。

  • pool – To allocate the resource for the specified handle, which must be valid until the handle/key is unregistered from I/O Queue.
  • ioque – The I/O Queue.
  • sock – The socket.
  • user_data – User data to be associated with the key, which can be retrieved later.
  • cb – Callback to be called when I/O opertion completes.
  • key – Pointer to receive the key to be associated with this socket. Subsequent I/O queue operation will need this key.

第二个attach是transport_attach/transport_attach2,这个attach会再传入rtp_cb和rtcp_cb,而这两个回调,会被on_rx_rtp调用,所以这里有两个回调。总结数据流方向,从网络收到数据,最后会进入attach传入的rtp_cb。但是这个rtp_cb什么时候设置,设置的是谁,这个是在 stream 中实现,下一篇再说。

pjmedia_transport_attach2调用流

pjmedia_transport_attach2-》transport_attach2-》tp_attach

tp_attach最重要就是设置了pjmedia_transport 的cb2 为on_rx_request,还有Copy remote RTP address

Port & Audio

结构体

pjmedia_port


/**
 * Port interface.
 */
typedef struct pjmedia_port
{
    pjmedia_port_info    info;              /**< Port information.  */

    /** Port data can be used by the port creator to attach arbitrary
     *  value to be associated with the port.
     */
    struct port_data {
        void            *pdata;             /**< Pointer data.      */
        long             ldata;             /**< Long data.         */
    } port_data;

    /**
     * Group lock.
     *
     * This is optional, but if this port is registered to the audio/video
     * conference bridge, the bridge will create one if the port has none.
     */
    pj_grp_lock_t       *grp_lock;

    /**
     * Get clock source.
     * This should only be called by #pjmedia_port_get_clock_src().
     */
    pjmedia_clock_src* (*get_clock_src)(struct pjmedia_port *this_port,
                                        pjmedia_dir dir);

    /**
     * Sink interface. 
     * This should only be called by #pjmedia_port_put_frame().
     */
    pj_status_t (*put_frame)(struct pjmedia_port *this_port, 
                             pjmedia_frame *frame);

    /**
     * Source interface. 
     * This should only be called by #pjmedia_port_get_frame().
     */
    pj_status_t (*get_frame)(struct pjmedia_port *this_port, 
                             pjmedia_frame *frame);

    /**
     * Called to destroy this port.
     */
    pj_status_t (*on_destroy)(struct pjmedia_port *this_port);

} pjmedia_port;

pjmedia_snd_port

struct pjmedia_snd_port
{
    int			 rec_id;
    int			 play_id;
    pj_uint32_t		 aud_caps;
    pjmedia_aud_param	 aud_param;
    pjmedia_aud_stream	*aud_stream;
    pjmedia_dir		 dir;
    pjmedia_port	*port;
 
    pjmedia_clock_src    cap_clocksrc,
                         play_clocksrc;
 
    unsigned		 clock_rate;
    unsigned		 channel_count;
    unsigned		 samples_per_frame;
    unsigned		 bits_per_sample;
    unsigned		 options;
    unsigned		 prm_ec_options;
 
 
    /* audio frame preview callbacks */
    void		*user_data;
    pjmedia_aud_play_cb  on_play_frame;
    pjmedia_aud_rec_cb   on_rec_frame;
};

pjmedia_snd_port_param

/**
 * This structure specifies the parameters to create the sound port.
 * Use pjmedia_snd_port_param_default() to initialize this structure with
 * default values (mostly zeroes)
 */
typedef struct pjmedia_snd_port_param
{
    /**
     * Base structure.
     */
    pjmedia_aud_param base;
    
    /**
     * Sound port creation options.
     */
    unsigned options;

    /**
     * Echo cancellation options/flags.
     */
    unsigned ec_options;

    /**
     * Arbitrary user data for playback and record preview callbacks below.
     */
    void *user_data;

    /**
     * Optional callback for audio frame preview right before queued to
     * the speaker.
     * Notes:
     * - application MUST NOT block or perform long operation in the callback
     *   as the callback may be executed in sound device thread
     * - when using software echo cancellation, application MUST NOT modify
     *   the audio data from within the callback, otherwise the echo canceller
     *   will not work properly.
     * - the return value of the callback will be ignored
     */
    pjmedia_aud_play_cb on_play_frame;

    /**
     * Optional callback for audio frame preview recorded from the microphone
     * before being processed by any media component such as software echo
     * canceller.
     * Notes:
     * - application MUST NOT block or perform long operation in the callback
     *   as the callback may be executed in sound device thread
     * - when using software echo cancellation, application MUST NOT modify
     *   the audio data from within the callback, otherwise the echo canceller
     *   will not work properly.
     * - the return value of the callback will be ignored
     */
    pjmedia_aud_rec_cb on_rec_frame;

} pjmedia_snd_port_param;

pjmedia_aud_param

/**
 * This structure specifies the parameters to open the audio stream.
 */
typedef struct pjmedia_aud_param
{
    /**
     * The audio direction. This setting is mandatory.
     */
    pjmedia_dir dir;

    /**
     * The audio recorder device ID. This setting is mandatory if the audio
     * direction includes input/capture direction.
     */
    pjmedia_aud_dev_index rec_id;

    /**
     * The audio playback device ID. This setting is mandatory if the audio
     * direction includes output/playback direction.
     */
    pjmedia_aud_dev_index play_id;

    /** 
     * Clock rate/sampling rate. This setting is mandatory. 
     */
    unsigned clock_rate;

    /** 
     * Number of channels. This setting is mandatory. 
     */
    unsigned channel_count;

    /** 
     * Number of samples per frame. This setting is mandatory. 
     */
    unsigned samples_per_frame;

    /** 
     * Number of bits per sample. This setting is mandatory. 
     */
    unsigned bits_per_sample;

    /** 
     * This flags specifies which of the optional settings are valid in this
     * structure. The flags is bitmask combination of pjmedia_aud_dev_cap.
     */
    unsigned flags;

    /** 
     * Set the audio format. This setting is optional, and will only be used
     * if PJMEDIA_AUD_DEV_CAP_EXT_FORMAT is set in the flags.
     */
    pjmedia_format ext_fmt;

    /**
     * Input latency, in milliseconds. This setting is optional, and will 
     * only be used if PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY is set in the flags.
     */
    unsigned input_latency_ms;

    /**
     * Input latency, in milliseconds. This setting is optional, and will 
     * only be used if PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY is set in the flags.
     */
    unsigned output_latency_ms;

    /**
     * Input volume setting, in percent. This setting is optional, and will 
     * only be used if PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING is set in 
     * the flags.
     */
    unsigned input_vol;

    /**
     * Output volume setting, in percent. This setting is optional, and will 
     * only be used if PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING is set in 
     * the flags.
     */
    unsigned output_vol;

    /** 
     * Set the audio input route/source. This setting is optional, and
     * will only be used if PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE/
     * PJMEDIA_AUD_DEV_CAP_INPUT_SOURCE is set in the flags.
     */
    pjmedia_aud_dev_route input_route;

    /** 
     * Set the audio output route. This setting is optional, and will only be
     * used if PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE is set in the flags.
     */
    pjmedia_aud_dev_route output_route;

    /**
     * Enable/disable echo canceller, if the device supports it. This setting
     * is optional, and will only be used if PJMEDIA_AUD_DEV_CAP_EC is set in
     * the flags.
     */
    pj_bool_t ec_enabled;

    /**
     * Set echo canceller tail length in milliseconds, if the device supports
     * it. This setting is optional, and will only be used if
     * PJMEDIA_AUD_DEV_CAP_EC_TAIL is set in the flags.
     */
    unsigned ec_tail_ms;

    /** 
     * Enable/disable PLC. This setting is optional, and will only be used
     * if PJMEDIA_AUD_DEV_CAP_PLC is set in the flags.
     */
    pj_bool_t plc_enabled;

    /** 
     * Enable/disable CNG. This setting is optional, and will only be used
     * if PJMEDIA_AUD_DEV_CAP_CNG is set in the flags.
     */
    pj_bool_t cng_enabled;

    /** 
     * Enable/disable VAD. This setting is optional, and will only be used
     * if PJMEDIA_AUD_DEV_CAP_VAD is set in the flags.
     */
    pj_bool_t vad_enabled;

} pjmedia_aud_param;

设备抽象

pjmedia_aud_stream 在设备的进一步抽象中绑定rec_cb play_cb

/**
 * This structure describes the audio device stream.
 */
struct pjmedia_aud_stream
{
    /** Internal data to be initialized by audio subsystem */
    struct {
        /** Driver index */
        unsigned drv_idx;
    } sys;

    /** Operations */
    pjmedia_aud_stream_op *op;
};

pjmedia_aud_stream_op

/**
 * Sound stream operations.
 */
typedef struct pjmedia_aud_stream_op
{
    /**
     * See #pjmedia_aud_stream_get_param()
     */
    pj_status_t (*get_param)(pjmedia_aud_stream *strm,
                             pjmedia_aud_param *param);

    /**
     * See #pjmedia_aud_stream_get_cap()
     */
    pj_status_t (*get_cap)(pjmedia_aud_stream *strm,
                           pjmedia_aud_dev_cap cap,
                           void *value);

    /**
     * See #pjmedia_aud_stream_set_cap()
     */
    pj_status_t (*set_cap)(pjmedia_aud_stream *strm,
                           pjmedia_aud_dev_cap cap,
                           const void *value);

    /**
     * See #pjmedia_aud_stream_start()
     */
    pj_status_t (*start)(pjmedia_aud_stream *strm);

    /**
     * See #pjmedia_aud_stream_stop().
     */
    pj_status_t (*stop)(pjmedia_aud_stream *strm);

    /**
     * See #pjmedia_aud_stream_destroy().
     */
    pj_status_t (*destroy)(pjmedia_aud_stream *strm);

} pjmedia_aud_stream_op;

factory相关


/**
 * Sound device factory operations.
 */
typedef struct pjmedia_aud_dev_factory_op
{
    /**
     * Initialize the audio device factory.
     *
     * @param f         The audio device factory.
     */
    pj_status_t (*init)(pjmedia_aud_dev_factory *f);

    /**
     * Close this audio device factory and release all resources back to the
     * operating system.
     *
     * @param f         The audio device factory.
     */
    pj_status_t (*destroy)(pjmedia_aud_dev_factory *f);

    /**
     * Get the number of audio devices installed in the system.
     *
     * @param f         The audio device factory.
     */
    unsigned (*get_dev_count)(pjmedia_aud_dev_factory *f);

    /**
     * Get the audio device information and capabilities.
     *
     * @param f         The audio device factory.
     * @param index     Device index.
     * @param info      The audio device information structure which will be
     *                  initialized by this function once it returns 
     *                  successfully.
     */
    pj_status_t (*get_dev_info)(pjmedia_aud_dev_factory *f, 
                                unsigned index,
                                pjmedia_aud_dev_info *info);

    /**
     * Initialize the specified audio device parameter with the default
     * values for the specified device.
     *
     * @param f         The audio device factory.
     * @param index     Device index.
     * @param param     The audio device parameter.
     */
    pj_status_t (*default_param)(pjmedia_aud_dev_factory *f,
                                 unsigned index,
                                 pjmedia_aud_param *param);

    /**
     * Open the audio device and create audio stream. See
     * #pjmedia_aud_stream_create()
     */
    pj_status_t (*create_stream)(pjmedia_aud_dev_factory *f,
                                 const pjmedia_aud_param *param,
                                 pjmedia_aud_rec_cb rec_cb,
                                 pjmedia_aud_play_cb play_cb,
                                 void *user_data,
                                 pjmedia_aud_stream **p_aud_strm);

    /**
     * Refresh the list of audio devices installed in the system.
     *
     * @param f         The audio device factory.
     */
    pj_status_t (*refresh)(pjmedia_aud_dev_factory *f);

} pjmedia_aud_dev_factory_op;


/**
 * This structure describes an audio device factory. 
 */
struct pjmedia_aud_dev_factory
{
    /** Internal data to be initialized by audio subsystem. */
    struct {
        /** Driver index */
        unsigned drv_idx;
    } sys;

    /** Operations */
    pjmedia_aud_dev_factory_op *op;
};

相关方法

初始化方法

(snd_port_param)pjmedia_snd_port_param_default

/* Initialize with default values (zero) */
PJ_DEF(void) pjmedia_snd_port_param_default(pjmedia_snd_port_param *prm)
{
    pj_bzero(prm, sizeof(*prm));
}

pjmedia_stream_get_port

PJ_DEF(pj_status_t) pjmedia_stream_get_port( pjmedia_stream *stream,
                                             pjmedia_port **p_port )
{
    *p_port = &stream->port;
    return PJ_SUCCESS;
}

stream->port,返回p_port

(aud_dev_default_param)pjmedia_aud_dev_default_param

/* API: Initialize the audio device parameters with default values for the
 * specified device.
 */
PJ_DEF(pj_status_t) pjmedia_aud_dev_default_param(pjmedia_aud_dev_index id,
                                                  pjmedia_aud_param *param)
{
    pjmedia_aud_dev_factory *f;
    unsigned index;
    pj_status_t status;

    PJ_ASSERT_RETURN(param && id!=PJMEDIA_AUD_INVALID_DEV, PJ_EINVAL);
    PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT);

    status = lookup_dev(id, &f, &index);  //Internal: lookup valid device id
    if (status != PJ_SUCCESS)
        return status;

    status = f->op->default_param(f, index, param);
    if (status != PJ_SUCCESS)
        return status;

    /* Normalize device IDs */
    make_global_index(f->sys.drv_idx, &param->rec_id);
    make_global_index(f->sys.drv_idx, &param->play_id);

    return PJ_SUCCESS;
}

Create port

pjmedia_snd_port_create

初始化pjmedia_snd_port_param的各种信息,调用pjmedia_snd_port_create2

pj_status_t pjmedia_snd_port_create_player(pj_pool_t *pool, int dev_id, unsigned int clock_rate, unsigned int channel_count, unsigned int samples_per_frame, unsigned int bits_per_sample, unsigned int options, pjmedia_snd_port **p_port)

Create unidirectional sound device port for playing audio streams with the specified parameters.

参数: pool – Pool to allocate sound port structure. index – Device index, or -1 to let the library choose the first available device. clock_rate – Sound device's clock rate to set. channel_count – Set number of channels, 1 for mono, or 2 for stereo. The channel count determines the format of the frame. samples_per_frame – Number of samples per frame. bits_per_sample – Set the number of bits per sample. The normal value for this parameter is 16 bits per sample. options – Options flag. p_port – Pointer to receive the sound device port instance.

pjmedia_snd_port_create2

用pjmedia_snd_port_creat中传入的pjmedia_snd_port_param,初始化pjmedia_snd_port(此时声音port创建),最后调用start_sound_device,Start the sound stream.

start_sound_device

  • Get device caps:获取dev_id,根据id调用函数pjmedia_aud_dev_get_info获取pjmedia_aud_dev_info

  • Process EC settings

  • Open the device

    设置了两个回调snd_rec_cb、snd_play_cb分别为 rec_cb、play_cb

        status = pjmedia_aud_stream_create(&param_copy,
                                           snd_rec_cb,
                                           snd_play_cb,
                                           snd_port,
                                           &snd_port->aud_stream);
    
  • Start sound stream.:pjmedia_aud_stream_start(snd_port->aud_stream);

pjmedia_aud_stream_create

先通过lookup_dev搜索rec_id、play_id设备对应的工厂,然后通过工厂创建设备f->op->create_stream并设置给设备两个回调函数 rec_cb、play_cb给设备抽象pjmedia_aud_stream的ca_cb、pb_cb,lookup_dev中aud_subsys已经存储了所有的音频设备是在创建媒体端点endpoint时初始化举例来说:初始化的时候创建媒体端点pjmedia_endpt,同时初始化了音频子系统aud_subsys,把各种类型的音频设备工厂添加到全局变量static pjmedia_aud_subsys aud_subsys;。这样当创建设备时,就可以遍历这些工厂,寻找合适的工厂,通过工厂创建设备实例。比如alsa类型的设备在alsa_dev.c

pjmedia_aud_stream_start

以alsa为例,在alsa_dev.c中,调用alsa_stream_start函数,在alsa_dev.c alsa_stream_start中,会创建播放和采集两条线程。

static pj_status_t alsa_stream_start (pjmedia_aud_stream *s)
{
    struct alsa_stream *stream = (struct alsa_stream*)s;
    pj_status_t status = PJ_SUCCESS;
 
    stream->quit = 0;
    if (stream->param.dir & PJMEDIA_DIR_PLAYBACK) {
	status = pj_thread_create (stream->pool,
				   "alsasound_playback",
				   pb_thread_func,
				   stream,
				   0, //ZERO,
				   0,
				   &stream->pb_thread);
 
 
    if (stream->param.dir & PJMEDIA_DIR_CAPTURE) {
	status = pj_thread_create (stream->pool,
				   "alsasound_playback",
				   ca_thread_func,
				   stream,
				   0, //ZERO,
				   0,
				   &stream->ca_thread);
 
    }
 
    return status;
}

以播放线程为例

static int pb_thread_func (void *arg)
{
    struct alsa_stream* stream = (struct alsa_stream*) arg;
    snd_pcm_t* pcm             = stream->pb_pcm;
    int size                   = stream->pb_buf_size;
    snd_pcm_uframes_t nframes  = stream->pb_frames;
    void* user_data            = stream->user_data;
    char* buf 		       = stream->pb_buf;
    pj_timestamp tstamp;
    int result;
 
    pj_bzero (buf, size);
    tstamp.u64 = 0;
 
 
    snd_pcm_prepare (pcm);
 
    while (!stream->quit) {
        pjmedia_frame frame;

        frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
        frame.buf = buf;
        frame.size = size;
        frame.timestamp.u64 = tstamp.u64;
        frame.bit_info = 0;

        result = stream->pb_cb (user_data, &frame);
        if (result != PJ_SUCCESS || stream->quit)
            break;

        if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
            pj_bzero (buf, size);

        result = snd_pcm_writei (pcm, buf, nframes);
        if (result == -EPIPE) {
            PJ_LOG (4,(THIS_FILE, "pb_thread_func: underrun!"));
            snd_pcm_prepare (pcm);
        } else if (result < 0) {
            PJ_LOG (4,(THIS_FILE, "pb_thread_func: error writing data!"));
        }

        tstamp.u64 += nframes;
    }
 
    snd_pcm_drain (pcm);
    TRACE_((THIS_FILE, "pb_thread_func: Stopped"));
    return PJ_SUCCESS;
}

播放线程先通过回调拿到待播放的音频数据stream->pb_cb ,然后写到声卡snd_pcm_writei。pb_cb就是sound_port.c中的play_cb,来看下play_cb的流程。

static pj_status_t play_cb(void *user_data, pjmedia_frame *frame)
{
    pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;
    pjmedia_port *port;
    const unsigned required_size = (unsigned)frame->size;
    pj_status_t status;
 
    port = snd_port->port;
    status = pjmedia_port_get_frame(port, frame);
 
    /* Invoke preview callback */
    if (snd_port->on_play_frame)
	(*snd_port->on_play_frame)(snd_port->user_data, frame);
 
    return PJ_SUCCESS;
}

通过pjmedia_port* port获取一帧数据

Stream

pjmedia_stream_info

对应sdp中的 m=字段 (媒体名称和传输地址)

m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 126
//m=audio说明本会话包含音频,9代表音频使用端口9来传输,但是在webrtc中一现在一般不使用,如果设置为0,代表不
//传输音频,UDP/TLS/RTP/SAVPF是表示用户来传输音频支持的协议,udp,tls,rtp代表使用udp来传输rtp包,并使用tls加密
//SAVPF代表使用srtcp的反馈机制来控制通信过程,后台111 103 104 9 0 8 106 105 13 126表示本会话音频支持的编码,后台几行会有详细补充说明
/**
 * This structure describes media stream information. Each media stream
 * corresponds to one "m=" line in SDP session descriptor, and it has
 * its own RTP/RTCP socket pair.
 */
typedef struct pjmedia_stream_info
{
    pjmedia_type        type;       /**< Media type (audio, video)          */
    pjmedia_tp_proto    proto;      /**< Transport protocol (RTP/AVP, etc.) */
    pjmedia_dir         dir;        /**< Media direction.                   */
    pj_sockaddr         local_addr; /**< Local RTP address                  */
    pj_sockaddr         rem_addr;   /**< Remote RTP address                 */
    pj_sockaddr         rem_rtcp;   /**< Optional remote RTCP address. If
                                         sin_family is zero, the RTP address
                                         will be calculated from RTP.       */
    pj_bool_t           rtcp_mux;   /**< Use RTP and RTCP multiplexing.     */
#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
    pj_bool_t           rtcp_xr_enabled;
                                    /**< Specify whether RTCP XR is enabled.*/
    pj_uint32_t         rtcp_xr_interval; /**< RTCP XR interval.            */
    pj_sockaddr         rtcp_xr_dest;/**<Additional remote RTCP XR address.
                                         This is useful for third-party (e.g:
                                         network monitor) to monitor the 
                                         stream. If sin_family is zero, 
                                         this will be ignored.              */
#endif
    pjmedia_rtcp_fb_info loc_rtcp_fb; /**< Local RTCP-FB info.              */
    pjmedia_rtcp_fb_info rem_rtcp_fb; /**< Remote RTCP-FB info.             */
    pjmedia_codec_info  fmt;        /**< Incoming codec format info.        */
    pjmedia_codec_param *param;     /**< Optional codec param.              */
    unsigned            tx_pt;      /**< Outgoing codec paylaod type.       */
    unsigned            rx_pt;      /**< Incoming codec paylaod type.       */
    unsigned            tx_maxptime;/**< Outgoing codec max ptime.          */
    int                 tx_event_pt;/**< Outgoing pt for telephone-events.  */
    int                 rx_event_pt;/**< Incoming pt for telephone-events.  */
    pj_uint32_t         ssrc;       /**< RTP SSRC.                          */
    pj_str_t            cname;      /**< RTCP CNAME.                        */
    pj_bool_t           has_rem_ssrc;/**<Has remote RTP SSRC?               */
    pj_uint32_t         rem_ssrc;   /**< Remote RTP SSRC.                   */
    pj_str_t            rem_cname;  /**< Remote RTCP CNAME.                 */
    pj_uint32_t         rtp_ts;     /**< Initial RTP timestamp.             */
    pj_uint16_t         rtp_seq;    /**< Initial RTP sequence number.       */
    pj_uint8_t          rtp_seq_ts_set;
                                    /**< Bitmask flags if initial RTP sequence 
                                         and/or timestamp for sender are set.
                                         bit 0/LSB : sequence flag 
                                         bit 1     : timestamp flag         */
    int                 jb_init;    /**< Jitter buffer init delay in msec.  
                                         (-1 for default).                  */
    int                 jb_min_pre; /**< Jitter buffer minimum prefetch
                                         delay in msec (-1 for default).    */
    int                 jb_max_pre; /**< Jitter buffer maximum prefetch
                                         delay in msec (-1 for default).    */
    int                 jb_max;     /**< Jitter buffer max delay in msec.   */
    pjmedia_jb_discard_algo jb_discard_algo;
                                    /**< Jitter buffer discard algorithm.   */

#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
    pj_bool_t           use_ka;     /**< Stream keep-alive and NAT hole punch
                                         (see #PJMEDIA_STREAM_ENABLE_KA)
                                         is enabled?                        */
    pjmedia_stream_ka_config ka_cfg;
                                    /**< Stream send kep-alive settings.    */
#endif
    pj_bool_t           rtcp_sdes_bye_disabled; 
                                    /**< Disable automatic sending of RTCP
                                         SDES and BYE.                      */
} pjmedia_stream_info;

pjmedia_stream

/**
 * This structure describes media stream.
 * A media stream is bidirectional media transmission between two endpoints.
 * It consists of two channels, i.e. encoding and decoding channels.
 * A media stream corresponds to a single "m=" line in a SDP session
 * description.
 */
struct pjmedia_stream
{
    pjmedia_endpt           *endpt;         /**< Media endpoint.            */
    pjmedia_codec_mgr       *codec_mgr;     /**< Codec manager instance.    */
    pjmedia_stream_info      si;            /**< Creation parameter.        */
    pjmedia_port             port;          /**< Port interface.            */
    pjmedia_channel         *enc;           /**< Encoding channel.          */
    pjmedia_channel         *dec;           /**< Decoding channel.          */

    pj_pool_t               *own_pool;      /**< Only created if not given  */

 
    pjmedia_dir              dir;           /**< Stream direction.          */
    void                    *user_data;     /**< User data.                 */
    pj_str_t                 cname;         /**< SDES CNAME                 */

    pjmedia_transport       *transport;     /**< Stream transport.          */

    pjmedia_codec           *codec;         /**< Codec instance being used. */
    pjmedia_codec_param      codec_param;   /**< Codec param.               */
    pj_int16_t              *enc_buf;       /**< Encoding buffer, when enc's
                                                 ptime is different than dec.
                                                 Otherwise it's NULL.       */

    unsigned                 enc_samples_per_pkt;
    unsigned                 enc_buf_size;  /**< Encoding buffer size, in
                                                 samples.                   */
    unsigned                 enc_buf_pos;   /**< First position in buf.     */
    unsigned                 enc_buf_count; /**< Number of samples in the
                                                 encoding buffer.           */

    pj_int16_t              *dec_buf;       /**< Decoding buffer.           */
    unsigned                 dec_buf_size;  /**< Decoding buffer size, in
                                                 samples.                   */
    unsigned                 dec_buf_pos;   /**< First position in buf.     */
    unsigned                 dec_buf_count; /**< Number of samples in the
                                                 decoding buffer.           */

    pj_uint16_t              dec_ptime;     /**< Decoder frame ptime in ms. */
    pj_uint8_t               dec_ptime_denum;/**< Decoder ptime denum.      */
    pj_bool_t                detect_ptime_change;
                                            /**< Detect decode ptime change */

    unsigned                 plc_cnt;       /**< # of consecutive PLC frames*/
    unsigned                 max_plc_cnt;   /**< Max # of PLC frames        */

    unsigned                 vad_enabled;   /**< VAD enabled in param.      */
    unsigned                 frame_size;    /**< Size of encoded base frame.*/
    pj_bool_t                is_streaming;  /**< Currently streaming?. This
                                                 is used to put RTP marker
                                                 bit.                       */
    pj_uint32_t              ts_vad_disabled;/**< TS when VAD was disabled. */
    pj_uint32_t              tx_duration;   /**< TX duration in timestamp.  */

    pj_mutex_t              *jb_mutex;
    pjmedia_jbuf            *jb;            /**< Jitter buffer.             */
    char                     jb_last_frm;   /**< Last frame type from jb    */
    unsigned                 jb_last_frm_cnt;/**< Last JB frame type counter*/
    unsigned                 soft_start_cnt;/**< Stream soft start counter */

    pjmedia_rtcp_session     rtcp;          /**< RTCP for incoming RTP.     */

    pj_uint32_t              rtcp_last_tx;  /**< RTCP tx time in timestamp  */
    pj_uint32_t              rtcp_interval; /**< Interval, in timestamp.    */
    pj_bool_t                initial_rr;    /**< Initial RTCP RR sent       */
    pj_bool_t                rtcp_sdes_bye_disabled;/**< Send RTCP SDES/BYE?*/
    void                    *out_rtcp_pkt;  /**< Outgoing RTCP packet.      */
    unsigned                 out_rtcp_pkt_size;
                                            /**< Outgoing RTCP packet size. */
    pj_int16_t              *zero_frame;    /**< Zero frame buffer.         */

    /* RFC 2833 DTMF transmission queue: */
    unsigned                 dtmf_duration; /**< DTMF duration(in timestamp)*/
    int                      tx_event_pt;   /**< Outgoing pt for dtmf.      */
    int                      tx_dtmf_count; /**< # of digits in tx dtmf buf.*/
    struct dtmf              tx_dtmf_buf[32];/**< Outgoing dtmf queue.      */

    /* Incoming DTMF: */
    int                      rx_event_pt;   /**< Incoming pt for dtmf.      */
    int                      last_dtmf;     /**< Current digit, or -1.      */
    pj_uint32_t              last_dtmf_dur; /**< Start ts for cur digit.    */
    pj_bool_t                last_dtmf_ended;
    unsigned                 rx_dtmf_count; /**< # of digits in dtmf rx buf.*/
    char                     rx_dtmf_buf[32];/**< Incoming DTMF buffer.     */

    /* DTMF callback */
    void                    (*dtmf_cb)(pjmedia_stream*, void*, int);
    void                     *dtmf_cb_user_data;

    void                    (*dtmf_event_cb)(pjmedia_stream*, void*,
                                             const pjmedia_stream_dtmf_event*);
    void                     *dtmf_event_cb_user_data;

#if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG!=0)
    /* Enable support to handle codecs with inconsistent clock rate
     * between clock rate in SDP/RTP & the clock rate that is actually used.
     * This happens for example with G.722 and MPEG audio codecs.
     */
    pj_bool_t                has_g722_mpeg_bug;
                                            /**< Flag to specify whether
                                                 normalization process
                                                 is needed                  */
    unsigned                 rtp_tx_ts_len_per_pkt;
                                            /**< Normalized ts length per packet
                                                 transmitted according to
                                                 'erroneous' definition     */
    unsigned                 rtp_rx_ts_len_per_frame;
                                            /**< Normalized ts length per frame
                                                 received according to
                                                 'erroneous' definition     */
    unsigned                 rtp_rx_last_cnt;/**< Nb of frames in last pkt  */
    unsigned                 rtp_rx_check_cnt;
                                            /**< Counter of remote timestamp
                                                 checking */
#endif


    pj_sockaddr              rem_rtp_addr;     /**< Remote RTP address      */
    unsigned                 rem_rtp_flag;     /**< Indicator flag about
                                                    packet from this addr.
                                                    0=no pkt, 1=good ssrc,
                                                    2=bad ssrc pkts         */
    unsigned                 rtp_src_cnt;      /**< How many pkt from
                                                    this addr.              */
    pj_uint32_t              rtp_rx_last_ts;        /**< Last received RTP
                                                         timestamp          */
    pj_uint32_t              rtp_tx_err_cnt;        /**< The number of RTP
                                                         send() error       */
    pj_uint32_t              rtcp_tx_err_cnt;       /**< The number of RTCP
                                                         send() error       */

    /* RTCP Feedback */
    pj_bool_t                send_rtcp_fb_nack;     /**< Send NACK?         */
    pjmedia_rtcp_fb_nack     rtcp_fb_nack;          /**< TX NACK state.     */
    int                      rtcp_fb_nack_cap_idx;  /**< RX NACK cap idx.   */


};

初始化pjmedia_stream_create

PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt,
                                           pj_pool_t *pool,
                                           const pjmedia_stream_info *info,
                                           pjmedia_transport *tp,
                                           void *user_data,
                                           pjmedia_stream **p_stream)

创建stream对象stream = PJ_POOL_ZALLOC_T(pool, pjmedia_stream);

好的,以下是所有变量以及它们的类型和描述的表格:

变量类型描述初始化
endptpjmedia_endpt*媒体端点。endpt 函数参数
codec_mgrpjmedia_codec_mgr*编解码器管理器实例。pjmedia_endpt_get_codec_mgr(endpt);
sipjmedia_stream_info流的创建参数。create参数info
portpjmedia_port端口接口。port.info初始化pjmedia_port_info_init ,stream->port.info.fmt
encpjmedia_channel*编码通道。f
decpjmedia_channel*解码通道。
own_poolpj_pool_t*只有在未给定时才会创建。
dirpjmedia_dir流的方向。info->dir
user_datavoid*用户数据。user_data参数
cnamepj_str_tSDES CNAME。
transportpjmedia_transport*流传输。
codecpjmedia_codec*正在使用的编解码器实例。pjmedia_codec_mgr_alloc_codec( stream->codec_mgr, &info->fmt, &stream->codec);
codec_parampjmedia_codec_param编解码器参数。stream->codec_param = *stream->si.param;或pjmedia_codec_mgr_get_default_param(stream->codec_mgr,&info>fmt,&stream>codec_param);
enc_bufpj_int16_t*编码缓冲区,当编码的 ptime 与解码的不同时有效。
enc_samples_per_pktunsigned每个包的编码样本数。
enc_buf_sizeunsigned编码缓冲区大小,以样本为单位。
enc_buf_posunsigned缓冲区中的第一个位置。
enc_buf_countunsigned编码缓冲区中的样本数。
dec_bufpj_int16_t*解码缓冲区。
dec_buf_sizeunsigned解码缓冲区大小,以样本为单位。
dec_buf_posunsigned缓冲区中的第一个位置。
dec_buf_countunsigned解码缓冲区中的样本数。
dec_ptimepj_uint16_t解码器帧的时间,以毫秒为单位。
dec_ptime_denumpj_uint8_t解码器帧的时间分母。
detect_ptime_changepj_bool_t检测解码 ptime 的变化。
plc_cntunsigned连续 PLC 帧的数量。
max_plc_cntunsigned最大的 PLC 帧数量。
vad_enabledunsigned参数中是否启用了 VAD。
frame_sizeunsigned编码基本帧的大小。
is_streamingpj_bool_t当前是否正在流式传输?
ts_vad_disabledpj_uint32_t禁用 VAD 时的时间戳。
tx_durationpj_uint32_t时间戳中的 TX 时长。
jb_mutexpj_mutex_t*抖动缓冲区互斥锁。status = pj_mutex_create_simple(pool, NULL, &stream->jb_mutex);
jbpjmedia_jbuf*抖动缓冲区。
jb_last_frmchar最后一帧的类型。
jb_last_frm_cntunsigned上一个 JB 帧类型的计数器。
soft_start_cntunsigned流软启动计数器。
rtcppjmedia_rtcp_session传入 RTP 的 RTCP。
rtcp_last_txpj_uint32_tRTCP 的上次发送时间戳。
rtcp_intervalpj_uint32_t间隔时间戳。
initial_rrpj_bool_t是否已发送初始的 RTCP RR。
rtcp_sdes_bye_disabledpj_bool_t是否发送 RTCP SDES/BYE?
out_rtcp_pktvoid*出站 RTCP 数据包。
out_rtcp_pkt_sizeunsigned出站 RTCP 数据包大小。
zero_framepj_int16_t*零帧缓冲区。
dtmf_durationunsignedDTMF 时长(时间戳)。
tx_event_ptintDTMF 的传输事件 PT。
tx_dtmf_countint发送 DTMF 缓冲区中的数字数量。
tx_dtmf_bufstruct dtmf[32]发送 DTMF 队列。
rx_event_ptint接收 DTMF 的事件 PT。
last_dtmfint当前数字,或 -1。
last_dtmf_durpj_uint32_t当前数字的开始时间戳。
last_dtmf_endedpj_bool_t上一个 DTMF 是否结束。
rx_dtmf_countunsigned接收 DTMF 缓冲区中的数字数量。
rx_dtmf_bufchar[32]接收 DTMF 缓冲区。
dtmf_cbvoid (*)(pjmedia_stream*, void*, int)DTMF 回调函数。
dtmf_cb_user_datavoid*DTMF 回调函数的用户数据。
dtmf_event_cbvoid (*)(pjmedia_stream*, void*, const pjmedia_stream_dtmf_event*)DTMF 事件回调函数。
dtmf_event_cb_user_datavoid*DTMF 事件回调函数的用户数据。
has_g722_mpeg_bugpj_bool_t是否存在 G722 MPEG Bug。
rtp_tx_ts_len_per_pktunsigned每个发送包的标准化 TS 长度。
rtp_rx_ts_len_per_frameunsigned每个接收帧的标准化 TS 长度。
rtp_rx_last_cntunsigned上一个包中的帧数。
rtp_rx_check_cntunsigned远程时间戳检查计数器。
rtcp_xr_last_txpj_uint32_t上次发送 RTCP XR 的时间戳。
rtcp_xr_intervalpj_uint32_tRTCP XR 的间隔时间戳。
rtcp_xr_destpj_sockaddr附加的远程 RTCP XR 目标。
rtcp_xr_dest_lenunsignedRTCP XR 目标地址的长度。
use_kapj_bool_t是否启用了流的保活机制。
ka_intervalunsigned发送保活的间隔。
last_frm_ts_sentpj_time_val上次发送包的时间。
start_ka_countunsigned创建后要发送的保活数量。
start_ka_intervalunsigned流创建后的保活发送间隔。
rem_rtp_addrpj_sockaddr远程 RTP 地址。
rem_rtp_flagunsigned来自该地址的数据包指示标志。
rtp_src_cntunsigned来自该地址的数据包数量。
trace_jb_fdpj_oshandle_t抖动跟踪文件句柄。
trace_jb_bufchar*抖动跟踪缓冲区。
rtp_rx_last_tspj_uint32_t上次接收的 RTP 时间戳。
rtp_tx_err_cntpj_uint32_tRTP 发送错误计数。
rtcp_tx_err_cntpj_uint32_tRTCP 发送错误计数。
send_rtcp_fb_nackpj_bool_t是否发送 RTCP 反馈 NACK?
rtcp_fb_nackpjmedia_rtcp_fb_nackTX NACK 状态。
rtcp_fb_nack_cap_idxintRX NACK 能力索引。
    stream->endpt = endpt;
    stream->codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
    stream->dir = info->dir;
    stream->user_data = user_data;
    stream->rtcp_interval = (PJMEDIA_RTCP_INTERVAL-500 + (pj_rand()%1000)) *
                            info->fmt.clock_rate / 1000;
    stream->rtcp_sdes_bye_disabled = info->rtcp_sdes_bye_disabled;

    stream->tx_event_pt = info->tx_event_pt ? info->tx_event_pt : -1;
    stream->rx_event_pt = info->rx_event_pt ? info->rx_event_pt : -1;
    stream->last_dtmf = -1;
    stream->jb_last_frm = PJMEDIA_JB_NORMAL_FRAME;
    stream->rtcp_fb_nack.pid = -1;
    stream->soft_start_cnt = PJMEDIA_STREAM_SOFT_START;
		stream->cname = info->cname;

Create mutex to protect jitter buffer:

codec: Create and initialize codec: pjmedia_codec_mgr_alloc_codec Get codec param: pjmedia_codec_mgr_get_default_param Init the codec pjmedia_codec_init(stream->codec, pool); Open the codec.

pjmedia_codec_open(stream->codec, &stream->codec_param);

    stream->dec_ptime = stream->codec_param.info.frm_ptime;
    stream->dec_ptime_denum = PJ_MAX(stream->codec_param.info.frm_ptime_denum,
                                     1);
    afd->bits_per_sample = 16;
    afd->frame_time_usec = stream->codec_param.info.frm_ptime *
                           stream->codec_param.setting.frm_per_pkt * 1000 /
                           stream->codec_param.info.frm_ptime_denum;
    stream->port.info.fmt.id = stream->codec_param.info.fmt_id;

重要stream对应port的回调

stream->port.put_frame = &put_frame;

stream->port.get_frame = &get_frame;

/* Init jitter buffer parameters: */

/* Create jitter buffer */

status = pjmedia_jbuf_create(pool, &stream->port.info.name,
                                 stream->frame_size,
                                 stream->codec_param.info.frm_ptime,
                                 jb_max, &stream->jb);

Create decoder channel

Create encoder channel

status = create_channel( pool, stream, PJMEDIA_DIR_ENCODING,
                             info->tx_pt, info, &stream->enc);

Init RTCP session:

Only attach transport when stream is ready.在attach之前初始化att_param 看两个回调

    att_param.rtp_cb2 = &on_rx_rtp;
    att_param.rtcp_cb = &on_rx_rtcp;
    stream->transport = tp;
    status = pjmedia_transport_attach2(tp, &att_param);
    if (status != PJ_SUCCESS)
        goto err_cleanup;

pjsua_call

/** 
 * Structure to be attached to invite dialog. 
 * Given a dialog "dlg", application can retrieve this structure
 * by accessing dlg->mod_data[pjsua.mod.id].
 */
struct pjsua_call
{
    unsigned             index;     /**< Index in pjsua array.              */
    pjsua_call_setting   opt;       /**< Call setting.                      */
    pj_bool_t            opt_inited;/**< Initial call setting has been set,
                                         to avoid different opt in answer.  */
    pjsip_inv_session   *inv;       /**< The invite session.                */
    void                *user_data; /**< User/application data.             */
    pjsip_status_code    last_code; /**< Last status code seen.             */
    pj_str_t             last_text; /**< Last status text seen.             */
    pj_time_val          start_time;/**< First INVITE sent/received.        */
    pj_time_val          res_time;  /**< First response sent/received.      */
    pj_time_val          conn_time; /**< Connected/confirmed time.          */
    pj_time_val          dis_time;  /**< Disconnect time.                   */
    pjsua_acc_id         acc_id;    /**< Account index being used.          */
    int                  secure_level;/**< Signaling security level.        */
    pjsua_call_hold_type call_hold_type; /**< How to do call hold.          */
    pj_bool_t            local_hold;/**< Flag for call-hold by local.       */
    void                *hold_msg;  /**< Outgoing hold tx_data.             */
    pj_str_t             cname;     /**< RTCP CNAME.                        */
    char                 cname_buf[16];/**< cname buffer.                   */

    unsigned             med_cnt;   /**< Number of media in SDP.            */
    pjsua_call_media     media[PJSUA_MAX_CALL_MEDIA]; /**< Array of media   */
    unsigned             med_prov_cnt;/**< Number of provisional media.     */
    pjsua_call_media     media_prov[PJSUA_MAX_CALL_MEDIA];
                                    /**< Array of provisional media.        */
    pj_bool_t            med_update_success;
                                    /**< Is media update successful?        */
    pj_bool_t            hanging_up;/**< Is call in the process of hangup?  */

    int                  audio_idx; /**< First active audio media.          */
    pj_mutex_t          *med_ch_mutex;/**< Media channel callback's mutex.  */
    pjsua_med_tp_state_cb   med_ch_cb;/**< Media channel callback.          */
    pjsua_med_tp_state_info med_ch_info;/**< Media channel info.            */

    pjsip_evsub         *xfer_sub;  /**< Xfer server subscription, if this
                                         call was triggered by xfer.        */
    pj_stun_nat_type     rem_nat_type; /**< NAT type of remote endpoint.    */

    char    last_text_buf_[128];    /**< Buffer for last_text.              */

    struct {
        int              retry_cnt;  /**< Retry count.                      */
    } lock_codec;                    /**< Data for codec locking when answer
                                          contains multiple codecs.         */

    struct {
        pjsip_dialog        *dlg;    /**< Call dialog.                      */
        pjmedia_sdp_session *rem_sdp;/**< Remote SDP.                       */
        pj_pool_t           *pool_prov;/**< Provisional pool.               */
        pj_bool_t            med_ch_deinit;/**< Media channel de-init-ed?   */
        union {
            struct {
                pjsua_msg_data  *msg_data;/**< Headers for outgoing INVITE. */
                pj_bool_t        hangup;  /**< Call is hangup?              */
            } out_call;
            struct {            
                call_answer      answers;/**< A list of call answers.       */
                pj_bool_t        hangup;/**< Call is hangup?                */
                pjsip_dialog    *replaced_dlg; /**< Replaced dialog.        */
            } inc_call;
        } call_var;
    } async_call;                      /**< Temporary storage for async
                                            outgoing/incoming call.         */

    pj_bool_t            rem_offerer;  /**< Was remote SDP offerer?         */
    unsigned             rem_aud_cnt;  /**< No of active audio in last remote
                                            offer.                          */
    unsigned             rem_vid_cnt;  /**< No of active video in last remote
                                            offer.                          */
    
    pj_bool_t            rx_reinv_async;/**< on_call_rx_reinvite() async.   */
    pj_timer_entry       reinv_timer;  /**< Reinvite retry timer.           */
    pj_bool_t            reinv_pending;/**< Pending until CONFIRMED state.  */
    pj_bool_t            reinv_ice_sent;/**< Has reinvite for ICE upd sent? */
    pjsip_rx_data       *incoming_data;/**< Cloned incoming call rdata.
                                            On pjsua2, when handling incoming 
                                            call, onCreateMediaTransport() will
                                            not be called since the call isn't
                                            created yet. This temporary 
                                            variable is used to handle such 
                                            case, see ticket #1916.         */

    struct {
        pj_bool_t        enabled;
        pj_bool_t        remote_sup;
        pj_bool_t        remote_dlg_est;
        pjsua_op_state   trickling;
        int              retrans18x_count;
        pj_bool_t        pending_info;
        pj_timer_entry   timer;
    } trickle_ice;

    pj_timer_entry       hangup_timer;  /**< Hangup retry timer.            */
    unsigned             hangup_retry;  /**< Number of hangup retries.      */
    unsigned             hangup_code;   /**< Hangup code.                   */
    pj_str_t             hangup_reason; /**< Hangup reason.                 */
    pjsua_msg_data      *hangup_msg_data;/**< Hangup message data.          */
};

pjsua_call_hold_type

/**
 * This enumeration specifies how we should offer call hold request to
 * remote peer. The default value is set by compile time constant
 * PJSUA_CALL_HOLD_TYPE_DEFAULT, and application may control the setting
 * on per-account basis by manipulating \a call_hold_type field in
 * #pjsua_acc_config.
 */
typedef enum pjsua_call_hold_type
{
    /**
     * This will follow RFC 3264 recommendation to use a=sendonly,
     * a=recvonly, and a=inactive attribute as means to signal call
     * hold status. This is the correct value to use.
     */
    PJSUA_CALL_HOLD_TYPE_RFC3264,

    /**
     * This will use the old and deprecated method as specified in RFC 2543,
     * and will offer c=0.0.0.0 in the SDP instead. Using this has many
     * drawbacks such as inability to keep the media transport alive while
     * the call is being put on hold, and should only be used if remote
     * does not understand RFC 3264 style call hold offer.
     */
    PJSUA_CALL_HOLD_TYPE_RFC2543

} pjsua_call_hold_type;

pjsua_acc

/**
 * Account
 */
typedef struct pjsua_acc
{
    pj_pool_t       *pool;          /**< Pool for this account.         */
    pjsua_acc_config cfg;           /**< Account configuration.         */
    pj_bool_t        valid;         /**< Is this account valid?         */

    int              index;         /**< Index in accounts array.       */
    pj_str_t         display;       /**< Display name, if any.          */
    pj_str_t         user_part;     /**< User part of local URI.        */
    pj_bool_t        is_sips;       /**< Local URI uses "sips"?         */
    pj_str_t         contact;       /**< Our Contact header.            */
    pj_str_t         reg_contact;   /**< Contact header for REGISTER.
                                         It may be different than acc
                                         contact if outbound is used    */
    pj_bool_t        contact_rewritten;
                                    /**< Contact rewrite has been done? */
    pjsip_host_port  via_addr;      /**< Address for Via header         */
    pjsip_transport *via_tp;        /**< Transport associated with
                                         the Via address                */

    pj_str_t         srv_domain;    /**< Host part of reg server.       */
    int              srv_port;      /**< Port number of reg server.     */

    pjsip_regc      *regc;          /**< Client registration session.   */
    pj_status_t      reg_last_err;  /**< Last registration error.       */
    int              reg_last_code; /**< Last status last register.     */

    pj_str_t         reg_mapped_addr;/**< Our addr as seen by reg srv.
                                          Only if allow_sdp_nat_rewrite
                                          is set                        */

    struct {
        pj_bool_t        active;    /**< Flag of reregister status.     */
        pj_timer_entry   timer;     /**< Timer for reregistration.      */
        void            *reg_tp;    /**< Transport for registration.    */
        unsigned         attempt_cnt; /**< Attempt counter.             */
    } auto_rereg;                   /**< Reregister/reconnect data.     */

    pj_timer_entry   ka_timer;      /**< Keep-alive timer for UDP.      */
    pjsip_transport *ka_transport;  /**< Transport for keep-alive.      */
    pj_sockaddr      ka_target;     /**< Destination address for K-A    */
    unsigned         ka_target_len; /**< Length of ka_target.           */

    pjsip_route_hdr  route_set;     /**< Complete route set inc. outbnd.*/
    pj_uint32_t      global_route_crc; /** CRC of global route setting. */
    pj_uint32_t      local_route_crc;  /** CRC of account route setting.*/

    unsigned         rfc5626_status;/**< SIP outbound status:
                                           0: not used
                                           1: requested
                                           2: acknowledged by servers   */
    pj_str_t         rfc5626_instprm;/**< SIP outbound instance param.  */
    pj_str_t         rfc5626_regprm;/**< SIP outbound reg param.        */
    unsigned         rfc5626_flowtmr;/**< SIP outbound flow timer.      */

    unsigned         cred_cnt;      /**< Number of credentials.         */
    pjsip_cred_info  cred[PJSUA_ACC_MAX_PROXIES]; /**< Complete creds.  */

    pj_bool_t        online_status; /**< Our online status.             */
    pjrpid_element   rpid;          /**< RPID element information.      */
    pjsua_srv_pres   pres_srv_list; /**< Server subscription list.      */
    pjsip_publishc  *publish_sess;  /**< Client publication session.    */
    pj_bool_t        publish_state; /**< Last published online status   */

    pjsip_evsub     *mwi_sub;       /**< MWI client subscription        */
    pjsip_dialog    *mwi_dlg;       /**< Dialog for MWI sub.            */

    pj_uint16_t      next_rtp_port; /**< Next RTP port to be used.      */
    pjsip_transport_type_e tp_type; /**< Transport type (for local acc or
                                         transport binding)             */
    pjsua_ip_change_op ip_change_op;/**< IP change process progress.    */
} pjsua_acc;

pjsua_data pjsua_var

/**
 * Global pjsua application data.
 */
struct pjsua_data
{

    /* Control: */
    pj_caching_pool      cp;        /**< Global pool factory.           */
    pj_pool_t           *pool;      /**< pjsua's private pool.          */
    pj_pool_t           *timer_pool;/**< pjsua's timer pool.            */
    pj_mutex_t          *mutex;     /**< Mutex protection for this data */
    unsigned             mutex_nesting_level; /**< Mutex nesting level. */
    pj_thread_t         *mutex_owner; /**< Mutex owner.                 */
    pjsua_state          state;     /**< Library state.                 */

    /* Logging: */
    pjsua_logging_config log_cfg;   /**< Current logging config.        */
    pj_oshandle_t        log_file;  /**<Output log file handle          */

    /* SIP: */
    pjsip_endpoint      *endpt;     /**< Global endpoint.               */
    pjsip_module         mod;       /**< pjsua's PJSIP module.          */
    pjsua_transport_data tpdata[8]; /**< Array of transports.           */
    pjsip_tp_state_callback old_tp_cb; /**< Old transport callback.     */

    /* Threading: */
    pj_bool_t            thread_quit_flag;  /**< Thread quit flag.      */
    pj_thread_t         *thread[4];         /**< Array of threads.      */

    /* STUN and resolver */
    pj_stun_config       stun_cfg;  /**< Global STUN settings.          */
    pj_sockaddr          stun_srv;  /**< Resolved STUN server address   */
    pj_status_t          stun_status; /**< STUN server status.          */
    pjsua_stun_resolve   stun_res;  /**< List of pending STUN resolution*/
    unsigned             stun_srv_idx; /**< Resolved STUN server index  */
    unsigned             stun_opt;  /**< STUN resolution option.        */
    pj_dns_resolver     *resolver;  /**< DNS resolver.                  */   

    /* UPnP */
    pj_status_t          upnp_status; /**< UPnP status.                 */

    /* Detected NAT type */
    pj_stun_nat_type     nat_type;      /**< NAT type.                  */
    pj_status_t          nat_status;    /**< Detection status.          */
    pj_bool_t            nat_in_progress; /**< Detection in progress    */

    /* List of outbound proxies: */
    pjsip_route_hdr      outbound_proxy;

    /* Account: */
    unsigned             acc_cnt;            /**< Number of accounts.   */
    pjsua_acc_id         default_acc;        /**< Default account ID    */
    pjsua_acc            acc[PJSUA_MAX_ACC]; /**< Account array.        */
    pjsua_acc_id         acc_ids[PJSUA_MAX_ACC]; /**< Acc sorted by prio*/

    /* Calls: */
    pjsua_config         ua_cfg;                /**< UA config.         */
    unsigned             call_cnt;              /**< Call counter.      */
    pjsua_call           calls[PJSUA_MAX_CALLS];/**< Calls array.       */
    pjsua_call_id        next_call_id;          /**< Next call id to use*/

    /* Buddy; */
    unsigned             buddy_cnt;                 /**< Buddy count.   */
    pjsua_buddy          buddy[PJSUA_MAX_BUDDIES];  /**< Buddy array.   */

    /* Presence: */
    pj_timer_entry       pres_timer;/**< Presence refresh timer.        */

    /* Media: */
    pjsua_media_config   media_cfg; /**< Media config.                  */
    pjmedia_endpt       *med_endpt; /**< Media endpoint.                */
    pjsua_conf_setting   mconf_cfg; /**< Additionan conf. bridge. param */
    pjmedia_conf        *mconf;     /**< Conference bridge.             */
    pj_bool_t            is_mswitch;/**< Are we using audio switchboard
                                         (a.k.a APS-Direct)             */

    /* Sound device */
    pjmedia_aud_dev_index cap_dev;  /**< Capture device ID.             */
    pjmedia_aud_dev_index play_dev; /**< Playback device ID.            */
    pj_uint32_t          aud_svmask;/**< Which settings to save         */
    pjmedia_aud_param    aud_param; /**< User settings to sound dev     */
    pj_bool_t            aud_open_cnt;/**< How many # device is opened  */
    pj_bool_t            no_snd;    /**< No sound (app will manage it)  */
    pj_pool_t           *snd_pool;  /**< Sound's private pool.          */
    pjmedia_snd_port    *snd_port;  /**< Sound port.                    */
    pj_timer_entry       snd_idle_timer;/**< Sound device idle timer.   */
    pjmedia_master_port *null_snd;  /**< Master port for null sound.    */
    pjmedia_port        *null_port; /**< Null port.                     */
    pj_bool_t            snd_is_on; /**< Media flow is currently active */
    unsigned             snd_mode;  /**< Sound device mode.             */

    /* Video device */
    pjmedia_vid_dev_index vcap_dev;  /**< Capture device ID.            */
    pjmedia_vid_dev_index vrdr_dev;  /**< Playback device ID.           */

    /* For keeping video device settings */
#if PJSUA_HAS_VIDEO
    pjmedia_vid_conf     *vid_conf;
    pj_uint32_t           vid_caps[PJMEDIA_VID_DEV_MAX_DEVS];
    pjmedia_vid_dev_param vid_param[PJMEDIA_VID_DEV_MAX_DEVS];
#endif

    /* File players: */
    unsigned             player_cnt;/**< Number of file players.        */
    pjsua_file_data      player[PJSUA_MAX_PLAYERS];/**< Array of players.*/

    /* File recorders: */
    unsigned             rec_cnt;   /**< Number of file recorders.      */
    pjsua_file_data      recorder[PJSUA_MAX_RECORDERS];/**< Array of recs.*/

    /* Video windows */
#if PJSUA_HAS_VIDEO
    pjsua_vid_win        win[PJSUA_MAX_VID_WINS]; /**< Array of windows */
#endif

    /* Timer entry and event list */
    pjsua_timer_list     active_timer_list;
    pjsua_timer_list     timer_list;
    pjsua_event_list     event_list;
    pj_mutex_t          *timer_mutex;
};

SDP

SDP协议简介

pjmedia_sdp_session

/**
 * This structure describes SDP session description. A SDP session descriptor
 * contains complete information about a session, and normally is exchanged
 * with remote media peer using signaling protocol such as SIP.
 */
struct pjmedia_sdp_session
{
    /** Session origin (o= line) */
    struct
    {
        pj_str_t    user;           /**< User                           */
        pj_uint_t   id;             /**< Session ID                     */
        pj_uint_t   version;        /**< Session version                */
        pj_str_t    net_type;       /**< Network type ("IN")            */
        pj_str_t    addr_type;      /**< Address type ("IP4", "IP6")    */
        pj_str_t    addr;           /**< The address.                   */
    } origin;

    pj_str_t           name;        /**< Subject line (s=)              */
    pjmedia_sdp_conn  *conn;        /**< Connection line (c=)           */
    unsigned           bandw_count; /**< Number of bandwidth info (b=)  */
    pjmedia_sdp_bandw *bandw[PJMEDIA_MAX_SDP_BANDW];
                                    /**< Bandwidth info array (b=)      */
    
    /** Session time (t= line)  */
    struct
    {
        pj_uint_t start;            /**< Start time.                    */
        pj_uint_t stop;             /**< Stop time.                     */
    } time;

    unsigned           attr_count;              /**< Number of attributes.  */
    pjmedia_sdp_attr  *attr[PJMEDIA_MAX_SDP_ATTR]; /**< Attributes array.   */

    unsigned           media_count;             /**< Number of media.       */
    pjmedia_sdp_media *media[PJMEDIA_MAX_SDP_MEDIA];    /**< Media array.   */

};

pjmedia_sdp_attr

/** 
 * Generic representation of attribute.
 */
struct pjmedia_sdp_attr
{
    pj_str_t            name;       /**< Attribute name.    */
    pj_str_t            value;      /**< Attribute value.   */
};

pjmedia_sdp_media

/**
 * This structure describes SDP media descriptor. A SDP media descriptor
 * starts with "m=" line and contains the media attributes and optional
 * connection line.
 */
struct pjmedia_sdp_media
{
    /** Media descriptor line ("m=" line) */
    struct
    {
        pj_str_t    media;              /**< Media type ("audio", "video")  */
        pj_uint16_t port;               /**< Port number.                   */
        unsigned    port_count;         /**< Port count, used only when >2  */
        pj_str_t    transport;          /**< Transport ("RTP/AVP")          */
        unsigned    fmt_count;          /**< Number of formats.             */
        pj_str_t    fmt[PJMEDIA_MAX_SDP_FMT];       /**< Media formats.     */
    } desc;

    pjmedia_sdp_conn   *conn;           /**< Optional connection info.      */
    unsigned            bandw_count;    /**< Number of bandwidth info.      */
    pjmedia_sdp_bandw  *bandw[PJMEDIA_MAX_SDP_BANDW]; /**< Bandwidth info.  */
    unsigned            attr_count;     /**< Number of attributes.          */
    pjmedia_sdp_attr   *attr[PJMEDIA_MAX_SDP_ATTR];   /**< Attributes.      */

};

pjmedia_sdp_conn

/**
 * This structure describes SDP connection info ("c=" line). 
 */
struct pjmedia_sdp_conn
{
    pj_str_t    net_type;       /**< Network type ("IN").               */
    pj_str_t    addr_type;      /**< Address type ("IP4", "IP6").       */
    pj_str_t    addr;           /**< The address.                       */
    pj_uint8_t  ttl;            /**< Multicast address TTL              */
    pj_uint8_t  no_addr;        /**< Multicast number of addresses      */
};

pjmedia_sdp_bandw

/**
 * This structure describes SDP bandwidth info ("b=" line). 
 */
typedef struct pjmedia_sdp_bandw
{
    pj_str_t    modifier;       /**< Bandwidth modifier.                */
    pj_uint32_t value;          /**< Bandwidth value.                   */
} pjmedia_sdp_bandw;

Dialog

img

Dialog Structure

 // This structure is used to describe dialog's participants, local and remote party.
struct pjsip_dlg_party
{
    pjsip_fromto_hdr *info; // From/To header, inc tag
    pj_uint32_t tag_hval; // Hashed value of the tag
    pjsip_contact_hdr *contact; // Contact header.
    pj_int32_t first_cseq; // First CSeq seen.
    pj_int32_t cseq; // Next sequence number.
};
// This structure describes basic dialog.
struct pjsip_dialog
{
    PJ_DECL_LIST_MEMBER(pjsip_dialog); // List node in dialog set.
// Static properties:
   char obj_name[PJ_MAX_OBJ_NAME]; // Log identification
    pj_pool_t *pool; // Dialog’s memory pool.
    pj_mutex_t *mutex; // Dialog's mutex.
    pjsip_user_agent *ua; // User agent instance.
    void *dlg_set; // The dialog set.
// Dialog session properties.
    pjsip_uri *target; // Current target.
    pjsip_dlg_party local; // Local party info.
    pjsip_dlg_party remote; // Remote party info.
    pjsip_role_e role; // Initial role.
    pj_bool_t secure; // Use secure transport?
    pjsip_cid_hdr *call_id; // Call-ID header.
    pjsip_route_hdr route_set; // Route set list.
    pjsip_auth_clt_sess auth_sess; // Client authentication session.
// Session Management
    int sess_count; // Session counter.
    int tsx_count; // Active transaction counter.
// Dialog usages
    unsigned usage_cnt; // Number of registered usages.
    pjsip_module *usage[PJSIP_MAX_MODULE]; // Usages, priority sorted
// Module specific data.
    void *mod_data[PJSIP_M AX_MODULE];
}

Session

invite session

/**
 * This structure describes the invite session.
 *
 * Note regarding the invite session's pools. The inv_sess used to have
 * only one pool, which is just a pointer to the dialog's pool. Ticket
 * https://github.com/pjsip/pjproject/issues/877 has found that the memory
 * usage will grow considerably everytime re-INVITE or UPDATE is
 * performed.
 *
 * Ticket #877 then created two more memory pools for the inv_sess, so
 * now we have three memory pools:
 *  - pool: to be used to allocate long term data for the session
 *  - pool_prov and pool_active: this is a flip-flop pools to be used
 *     interchangably during re-INVITE and UPDATE. pool_prov is
 *     "provisional" pool, used to allocate SDP offer or answer for
 *     the re-INVITE and UPDATE. Once SDP negotiation is done, the
 *     provisional pool will be made as the active pool, then the
 *     existing active pool will be reset, to release the memory
 *     back to the OS. So these pool's lifetime is synchronized to
 *     the SDP offer-answer negotiation.
 *
 * Higher level application such as PJSUA-LIB has been modified to
 * make use of these flip-flop pools, i.e. by creating media objects
 * from the provisional pool rather than from the long term pool.
 *
 * Other applications that want to use these pools must understand
 * that the flip-flop pool's lifetimes are synchronized to the
 * SDP offer-answer negotiation.
 *
 * The lifetime of this session is controlled by the reference counter in this
 * structure, which is manipulated by calling #pjsip_inv_add_ref and
 * #pjsip_inv_dec_ref. When the reference counter has reached zero, then
 * this session will be destroyed.
 */
struct pjsip_inv_session
{
    char                 obj_name[PJ_MAX_OBJ_NAME]; /**< Log identification */
    pj_pool_t           *pool;                      /**< Long term pool.    */
    pj_pool_t           *pool_prov;                 /**< Provisional pool   */
    pj_pool_t           *pool_active;               /**< Active/current pool*/
    pjsip_inv_state      state;                     /**< Invite sess state. */
    pj_bool_t            cancelling;                /**< CANCEL requested   */
    pj_bool_t            pending_cancel;            /**< Wait to send CANCEL*/
    pjsip_tx_data       *pending_bye;               /**< BYE to send later  */
    pjsip_status_code    cause;                     /**< Disconnect cause.  */
    pj_str_t             cause_text;                /**< Cause text.        */
    pj_bool_t            notify;                    /**< Internal.          */
    pj_bool_t            sdp_done_early_rel;        /**< Nego done in early
                                                         med was reliable?  */
    unsigned             cb_called;                 /**< Cb has been called */
    pjsip_dialog        *dlg;                       /**< Underlying dialog. */
    pjsip_role_e         role;                      /**< Invite role.       */
    unsigned             options;                   /**< Options in use.    */
    pjmedia_sdp_neg     *neg;                       /**< Negotiator.        */
    unsigned             sdp_neg_flags;             /**< SDP neg flags.     */
    pjsip_transaction   *invite_tsx;                /**< 1st invite tsx.    */
    pjsip_tx_data       *invite_req;                /**< Saved invite req   */
    pjsip_tx_data       *last_answer;               /**< Last INVITE resp.  */
    pjsip_tx_data       *last_ack;                  /**< Last ACK request   */
    pj_int32_t           last_ack_cseq;             /**< CSeq of last ACK   */
    void                *mod_data[PJSIP_MAX_MODULE];/**< Modules data.      */
    struct pjsip_timer  *timer;                     /**< Session Timers.    */
    pj_bool_t            following_fork;            /**< Internal, following
                                                         forked media?      */
    pj_atomic_t         *ref_cnt;                   /**< Reference counter. */
    pj_bool_t            updated_sdp_answer;        /**< SDP answer just been
                                                         updated?           */
};

transaction

Invite session data to be attached to transaction.

/* Invite session data to be attached to transaction. */
struct tsx_inv_data
{
    pjsip_inv_session   *inv;       /* The invite session                   */
    pj_bool_t            sdp_done;  /* SDP negotiation done for this tsx?   */
    pj_bool_t            retrying;  /* Resend (e.g. due to 401/407)         */
    pj_str_t             done_tag;  /* To tag in RX response with answer    */
    pj_bool_t            done_early;/* Negotiation was done for early med?  */
    pj_bool_t            done_early_rel;/* Early med was realiable?         */
    pj_bool_t            has_sdp;   /* Message with SDP?                    */
};

pjsip_transaction

/**
 * This structure describes SIP transaction object. The transaction object
 * is used to handle both UAS and UAC transaction.
 */
struct pjsip_transaction
{
    /*
     * Administrivia
     */
    pj_pool_t                  *pool;           /**< Pool owned by the tsx. */
    pjsip_module               *tsx_user;       /**< Transaction user.      */
    pjsip_endpoint             *endpt;          /**< Endpoint instance.     */
    pj_bool_t                   terminating;    /**< terminate() was called */
    pj_grp_lock_t              *grp_lock;       /**< Transaction grp lock.  */
    pj_mutex_t                 *mutex_b;        /**< Second mutex to avoid
                                                     deadlock. It is used to
                                                     protect timer.         */

    /*
     * Transaction identification.
     */
    char                        obj_name[PJ_MAX_OBJ_NAME];  /**< Log info.  */
    pjsip_role_e                role;           /**< Role (UAS or UAC)      */
    pjsip_method                method;         /**< The method.            */
    pj_int32_t                  cseq;           /**< The CSeq               */
    pj_str_t                    transaction_key;/**< Hash table key.        */
    pj_str_t                    transaction_key2;/**< Hash table key (2)   
                                                     for merged requests
                                                     tsx lookup.            */
    pj_uint32_t                 hashed_key;     /**< Key's hashed value.    */
    pj_uint32_t                 hashed_key2;    /**< Key's hashed value (2).*/
    pj_str_t                    branch;         /**< The branch Id.         */

    /*
     * State and status.
     */
    int                         status_code;    /**< Last status code seen. */
    pj_str_t                    status_text;    /**< Last reason phrase.    */
    pjsip_tsx_state_e           state;          /**< State.                 */
    int                         handle_200resp; /**< UAS 200/INVITE  retrsm.*/
    int                         tracing;        /**< Tracing enabled?       */

    /** Handler according to current state. */
    pj_status_t (*state_handler)(struct pjsip_transaction *, pjsip_event *);

    /*
     * Transport.
     */
    pjsip_transport            *transport;      /**< Transport to use.      */
    pj_bool_t                   is_reliable;    /**< Transport is reliable. */
    pj_sockaddr                 addr;           /**< Destination address.   */
    int                         addr_len;       /**< Address length.        */
    pjsip_response_addr         res_addr;       /**< Response address.      */
    unsigned                    transport_flag; /**< Miscelaneous flag.     */
    pj_status_t                 transport_err;  /**< Internal error code.   */
    pjsip_tpselector            tp_sel;         /**< Transport selector.    */
    pjsip_tx_data              *pending_tx;     /**< Tdata which caused
                                                     pending transport flag
                                                     to be set on tsx.      */
    pjsip_tp_state_listener_key *tp_st_key;     /**< Transport state listener
                                                     key.                   */

    /*
     * Messages and timer.
     */
    pjsip_tx_data              *last_tx;        /**< Msg kept for retrans.  */
    int                         retransmit_count;/**< Retransmission count. */
    pj_timer_entry              retransmit_timer;/**< Retransmit timer.     */
    pj_timer_entry              timeout_timer;  /**< Timeout timer.         */

    /** Module specific data. */
    void                       *mod_data[PJSIP_MAX_MODULE];
};
graph TD;
    A-->B;
    A-->C;
    B-->D;
    C-->D;

transport

/**
 * This structure represent the "public" interface of a SIP transport.
 * Applications normally extend this structure to include transport
 * specific members.
 */
struct pjsip_transport
{
    char                    obj_name[PJ_MAX_OBJ_NAME];  /**< Name. */

    pj_pool_t              *pool;           /**< Pool used by transport.    */
    pj_atomic_t            *ref_cnt;        /**< Reference counter.         */
    pj_lock_t              *lock;           /**< Lock object.               */
    pj_grp_lock_t          *grp_lock;       /**< Group lock for sync with
                                                 ioqueue and timer.         */
    pj_bool_t               tracing;        /**< Tracing enabled?           */
    pj_bool_t               is_shutdown;    /**< Being shutdown?            */
    pj_bool_t               is_destroying;  /**< Destroy in progress?       */

    /** Key for indexing this transport in hash table. */
    pjsip_transport_key     key;

    char                   *type_name;      /**< Type name.                 */
    unsigned                flag;           /**< #pjsip_transport_flags_e   */
    char                   *info;           /**< Transport info/description.*/

    int                     addr_len;       /**< Length of addresses.       */
    pj_sockaddr             local_addr;     /**< Bound address.             */
    pjsip_host_port         local_name;     /**< Published name (eg. STUN). */
    pjsip_host_port         remote_name;    /**< Remote address name.       */
    pjsip_transport_dir     dir;            /**< Connection direction.      */
    
    pjsip_endpoint         *endpt;          /**< Endpoint instance.         */
    pjsip_tpmgr            *tpmgr;          /**< Transport manager.         */
    pjsip_tpfactory        *factory;        /**< Factory instance. Note: it
                                                 may be invalid/shutdown.   */
    pj_timer_entry          idle_timer;     /**< Timer when ref cnt is zero.*/

    pj_timestamp            last_recv_ts;   /**< Last time receiving data.  */
    pj_size_t               last_recv_len;  /**< Last received data length. */

    void                   *data;           /**< Internal transport data.   */
    unsigned                initial_timeout;/**< Initial timeout interval
                                                 to be applied to incoming
                                                 TCP/TLS transports when no
                                                 valid data received after
                                                 a successful connection.   */

    /**
     * Function to be called by transport manager to send SIP message.
     *
     * @param transport     The transport to send the message.
     * @param packet        The buffer to send.
     * @param length        The length of the buffer to send.
     * @param op_key        Completion token, which will be supplied to
     *                      caller when pending send operation completes.
     * @param rem_addr      The remote destination address.
     * @param addr_len      Size of remote address.
     * @param callback      If supplied, the callback will be called
     *                      once a pending transmission has completed. If
     *                      the function completes immediately (i.e. return
     *                      code is not PJ_EPENDING), the callback will not
     *                      be called.
     *
     * @return              Should return PJ_SUCCESS only if data has been
     *                      succesfully queued to operating system for 
     *                      transmission. Otherwise it may return PJ_EPENDING
     *                      if the underlying transport can not send the
     *                      data immediately and will send it later, which in
     *                      this case caller doesn't have to do anything 
     *                      except wait the calback to be called, if it 
     *                      supplies one.
     *                      Other return values indicate the error code.
     */
    pj_status_t (*send_msg)(pjsip_transport *transport, 
                            pjsip_tx_data *tdata,
                            const pj_sockaddr_t *rem_addr,
                            int addr_len,
                            void *token,
                            pjsip_transport_callback callback);

    /**
     * Instruct the transport to initiate graceful shutdown procedure.
     * After all objects release their reference to this transport,
     * the transport will be deleted.
     *
     * Note that application MUST use #pjsip_transport_shutdown() instead.
     *
     * @param transport     The transport.
     *
     * @return              PJ_SUCCESS on success.
     */
    pj_status_t (*do_shutdown)(pjsip_transport *transport);

    /**
     * Forcefully destroy this transport regardless whether there are
     * objects that currently use this transport. This function should only
     * be called by transport manager or other internal objects (such as the
     * transport itself) who know what they're doing. Application should use
     * #pjsip_transport_shutdown() instead.
     *
     * @param transport     The transport.
     *
     * @return              PJ_SUCCESS on success.
     */
    pj_status_t (*destroy)(pjsip_transport *transport);

    /*
     * Application may extend this structure..
     */
};

Data structure for sending outgoing message

/**
 * Data structure for sending outgoing message. Application normally creates
 * this buffer by calling #pjsip_endpt_create_tdata.
 *
 * The lifetime of this buffer is controlled by the reference counter in this
 * structure, which is manipulated by calling #pjsip_tx_data_add_ref and
 * #pjsip_tx_data_dec_ref. When the reference counter has reached zero, then
 * this buffer will be destroyed.
 *
 * A transaction object normally will add reference counter to this buffer
 * when application calls #pjsip_tsx_send_msg, because it needs to keep the
 * message for retransmission. The transaction will release the reference
 * counter once its state has reached final state.
 */
struct pjsip_tx_data
{
    /** This is for transmission queue; it's managed by transports. */
    PJ_DECL_LIST_MEMBER(struct pjsip_tx_data);

    /** Memory pool for this buffer. */
    pj_pool_t           *pool;

    /** A name to identify this buffer. */
    char                 obj_name[PJ_MAX_OBJ_NAME];

    /** Short information describing this buffer and the message in it. 
     *  Application should use #pjsip_tx_data_get_info() instead of
     *  directly accessing this member.
     */
    char                *info;

    /** For response message, this contains the reference to timestamp when 
     *  the original request message was received. The value of this field
     *  is set when application creates response message to a request by
     *  calling #pjsip_endpt_create_response.
     */
    pj_time_val          rx_timestamp;

    /** The transport manager for this buffer. */
    pjsip_tpmgr         *mgr;

    /** Ioqueue asynchronous operation key. */
    pjsip_tx_data_op_key op_key;

    /** Lock object. */
    pj_lock_t           *lock;

    /** The message in this buffer. */
    pjsip_msg           *msg;

    /** Strict route header saved by #pjsip_process_route_set(), to be
     *  restored by #pjsip_restore_strict_route_set().
     */
    pjsip_route_hdr     *saved_strict_route;

    /** Buffer to the printed text representation of the message. When the
     *  content of this buffer is set, then the transport will send the content
     *  of this buffer instead of re-printing the message structure. If the
     *  message structure has changed, then application must invalidate this
     *  buffer by calling #pjsip_tx_data_invalidate_msg.
     */
    pjsip_buffer         buf;

    /** Reference counter. */
    pj_atomic_t         *ref_cnt;

    /** Being processed by transport? */
    int                  is_pending;

    /** Transport manager internal. */
    void                *token;

    /** Callback to be called when this tx_data has been transmitted.   */
    void               (*cb)(void*, pjsip_tx_data*, pj_ssize_t);

    /** Destination information, to be used to determine the network address
     *  of the message. For a request, this information is  initialized when
     *  the request is sent with #pjsip_endpt_send_request_stateless() and
     *  network address is resolved. For CANCEL request, this information
     *  will be copied from the original INVITE to make sure that the CANCEL
     *  request goes to the same physical network address as the INVITE
     *  request.
     */
    struct
    {
        /** Server name. 
         */
        pj_str_t                 name;

        /** Server addresses resolved. 
         */
        pjsip_server_addresses   addr;

        /** Current server address being tried. 
         */
        unsigned cur_addr;

    } dest_info;

    /** Transport information, only valid during on_tx_request() and 
     *  on_tx_response() callback.
     */
    struct
    {
        pjsip_transport     *transport;     /**< Transport being used.  */
        pj_sockaddr          dst_addr;      /**< Destination address.   */
        int                  dst_addr_len;  /**< Length of address.     */
        char                 dst_name[PJ_INET6_ADDRSTRLEN]; /**< Destination address.   */
        int                  dst_port;      /**< Destination port.      */
    } tp_info;

    /** 
     * Transport selector, to specify which transport to be used. 
     * The value here must be set with pjsip_tx_data_set_transport(),
     * to allow reference counter to be set properly.
     */
    pjsip_tpselector        tp_sel;

    /**
     * Special flag to indicate that this transmit data is a request that has
     * been updated with proper authentication response and is ready to be
     * sent for retry.
     */
    pj_bool_t               auth_retry;

    /**
     * Arbitrary data attached by PJSIP modules.
     */
    void                    *mod_data[PJSIP_MAX_MODULE];

    /**
     * If via_addr is set, it will be used as the "sent-by" field of the
     * Via header for outgoing requests as long as the request uses via_tp
     * transport. Normally application should not use or access these fields.
     */
    pjsip_host_port          via_addr;      /**< Via address.           */
    const void              *via_tp;        /**< Via transport.         */
};

PJSIP log

日志用法

使用之前需要初始化日志,但这一步是内部函数pj_init自己调用的,应用程序无需显示调用。

/**
 * Internal function to be called by pj_init()
 */

pj_status_t pj_log_init(void);
*   PJ_LOG(3, ("main.c", "Starting hello..."));

*   PJ_LOG(3, ("main.c", "Hello world from process %d", pj_getpid()));

3表示日志级别,级别越高值越小,main.c表示日志发送者,即写日志的模块,类似于tag,后面就是格式化字符串。

日志API

#define PJ_LOG(level,arg)	do { \

				    if (level <= pj_log_get_level()) { \

						pj_log_wrapper_##level(arg); \

				    } \
				    
				} while (0)

日志一般使用上面的宏,该宏会判断输入等级是否高于设置的等级(等级越高值越小,所以是<=),是则调用对应的等级函数。

pj_log_wrapper_1到pj_log_wrapper_6最终都是调用最底层的函数pj_log。

/**
 * Write to log.
 *
 * @param sender    Source of the message.
 * @param level	    Verbosity level.
 * @param format    Format.
 * @param marker    Marker.
 */

PJ_DECL(void) pj_log(const char *sender, int level, const char *format, va_list marker);

pj_log根据样式的配置组织字符串,最后调用log_writer写入不同的对象。

日志配置

日志的配置非常灵活,有哪些配置呢

/**
 * Log decoration flag, to be specified with #pj_log_set_decor().
 */

enum pj_log_decoration
{

    PJ_LOG_HAS_DAY_NAME   =    1, /**< Include day name [default: no] 	      */

    PJ_LOG_HAS_YEAR       =    2, /**< Include year digit [no]		      */

    PJ_LOG_HAS_MONTH	  =    4, /**< Include month [no]		      */

    PJ_LOG_HAS_DAY_OF_MON =    8, /**< Include day of month [no]	      */

    PJ_LOG_HAS_TIME	  =   16, /**< Include time [yes]		      */

    PJ_LOG_HAS_MICRO_SEC  =   32, /**< Include microseconds [yes]             */

    PJ_LOG_HAS_SENDER	  =   64, /**< Include sender in the log [yes] 	      */

    PJ_LOG_HAS_NEWLINE	  =  128, /**< Terminate each call with newline [yes] */

    PJ_LOG_HAS_CR	  =  256, /**< Include carriage return [no] 	      */

    PJ_LOG_HAS_SPACE	  =  512, /**< Include two spaces before log [yes]    */

    PJ_LOG_HAS_COLOR	  = 1024, /**< Colorize logs [yes on win32]	      */

    PJ_LOG_HAS_LEVEL_TEXT = 2048, /**< Include level text string [no]	      */

    PJ_LOG_HAS_THREAD_ID  = 4096, /**< Include thread identification [no]     */

    PJ_LOG_HAS_THREAD_SWC = 8192, /**< Add mark when thread has switched [yes]*/

    PJ_LOG_HAS_INDENT     =16384  /**< Indentation. Say yes! [yes]            */

};

从上面看,比较重要的有时间格式,发送者tag,和线程名字,可以通过pj_log_set_decor接口设置,比如。

int param_log_decor = PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_TIME | PJ_LOG_HAS_MICRO_SEC;

pj_log_set_decor(param_log_decor);

设置日志等级

PJ_DECL(void) pj_log_set_level(int level);

设置颜色

PJ_DECL(void) pj_log_set_color(int level, pj_color_t color);

设置缩进排版

/**
 * Add indentation to log message. Indentation will add PJ_LOG_INDENT_CHAR
 * before the message, and is useful to show the depth of function calls.
 *
 * @param indent    The indentation to add or substract. Positive value
 * 		    adds current indent, negative value subtracts current
 * 		    indent.
 */

PJ_DECL(void) pj_log_add_indent(int indent);

/**
 * Push indentation to the right by default value (PJ_LOG_INDENT).
 */

PJ_DECL(void) pj_log_push_indent(void);

/**
 * Pop indentation (to the left) by default value (PJ_LOG_INDENT).
 */

PJ_DECL(void) pj_log_pop_indent(void);

输出对象

pj_log最终调用log_writer写到输出对象,log_writer是一个函数回调指针

/**
 * Signature for function to be registered to the logging subsystem to
 * write the actual log message to some output device.
 *
 * @param level	    Log level.
 * @param data	    Log message, which will be NULL terminated.
 * @param len	    Message length.
 */

typedef void pj_log_func(int level, const char *data, int len);

static pj_log_func *log_writer = &pj_log_write;

pj_log_write则在编译时指定编译为printk、printf等。对应log_write_printk.c、log_write_stdout.c,可以看出,如果我们想把日志保存的文件,自己实现一个pj_log_write函数,在函数里写到文件即可。