本文介绍了如何使用JRtpLib3.5.0库中的函数和类对象。
1、RTP
1.1、初始化
生成RTPSession对象:RTPSessionSession
初始化RTPSessio:Session.Create(RTPSessionParams,RTPUDPv4TransmissionParams),RTPSessionParams的关键作用就是设置时间戳,形式如下:
RTPSessionParamsSessionParams;
SessionParams.SetOwnTimestampUnit(1.0/8000.0)(1/8000表示:当RTP会话传输8000HZ数据的时间戳)
RTPUDPv4TransmissionParams表示传输协议采用UDP、IP采用IPV4时端口参数设置。形式如下:
RTPUDPv4TransmissionParams transparams ;
transparams . SetPortbase ( 8 0 0 0 ) ;(8000表示端口号)。
注意:Create一个重载函数,有多种形式。而第二个参数的形式根据传输协议和IP地址版本来定。
Session对象创建示例:
RTPSession session; IntStatus = session.Create(SessionParams,ransparams); if(Status < 0) { std::err<<RTPGetErrorString(Status)<<std::endl; exit(-1); } |
Create调用成功后,下面就要增加目的地址,使用RTPSession成员函数AddDestination,如果使用IPV4地址则其参数类型为RTPIPV4Address。程序示例:
u_i n t 8 _t l o c a l i p [ ]={1 2 7 , 0 , 0 , 1}; RTPIPv4Address addr ( l o c a l i p , 9 0 0 0 ) ; status = session.AddDestination ( addr ) ; if (status < 0) { std : : cerr << RTPGetErrorString ( st a t u s ) << s td : : endl; exit(?1); } |
2、数据发送
初始化后,接下来可以调用 RTPSession 类的数据发送函数 SendPacket(),向所有目的地址发送流媒体数据。这是一个重载函数,具有多种调用形式(参看 RTPSession 类对该函数的定义),例如在下面的调用形式:
int SendPacket(void *data,int len,unsigned char pt,boolmark,unsigned long timestampinc);
其中第一个参数是待发送的数据缓冲区地址,第二个参数是待发送数据的长度,后面依次是 RTP 负载类型、标识和时基增量。
对于同一个 RTP 会话双方,负载类型、标识和时戳增量相同,可以对它们提前设置,调用 RTPSession 类的SetDefaultPayloadType()、SetDefaultMark()和SetDefaultTimeStampIncrement()函数就可实现。在设会话双方,负载类型、标识和时戳增量,就可以使用SendPacket的简化类型:SendParket(void *data, intlen)。
3、数据接收
接收实时流媒体数据,通过调用 RTPSession 类的 PollData()函数来接收收到的 RTP 或RTCP 数据报。同一个RTP 会话中允许有多个参与者( 源),通过调用GotoFirstSourceWithData()和 GotoNextSourceWithData()函数来实现源的遍历,通过 GetNextPacket()函数提取 RTP 数据包。数据包接受以RTPSession的成员函数BeginDataAccess开始、EndDataAccess结束示例:
sess.BeginDataAccess(); if(sess.GotoFirstSourceWithData()) { do{ RTPPacket *pack? while ((pack = sess.GetNextPacket()) != NULL) { printf("Got packet !\n")? delete pack? delete pack; } } while (sess.GotoNextSourceWithData())? } sess.EndDataAccess(); |
4、数据传输结束,释放资源
RTPTime(10.0);//释放资源最多等待十秒
sess.BYEDestroy(delay, Time' s up,9);//释放占有的
1.开始
1.1 创建一个session
为了使用这个库,你首先需要构建一个session,类型为RTPSession,然后调用create()成员函数创建,它带有一个参数,是端口号,例子:
#i nclude"rtpsession.h"
int main(void)
{
RTPSession sess;
sess.Create(5000);
return 0;
}
1.2 错误
在这个库中,如果条拥有错误发生,所有的函数都会返回一个int 或者 false,你可以通过调用RTPGetErrorString()函数来判断是否出错,有错,他会返回详细的错误信息,否则为0。例子:
#include<stdio.h>
#include "rtpsession.h"
int main(void)
{
RTPSession sess;
int status;
char *msg;
status = sess.Create(5000);
msg = RTPGetErrorString(status);
printf("%s\n",msg);
return 0;
}
1.3 其他初始化
你很可能会用到时间戳单元,设置时间戳单元调用函数RTPSession 的成员函数SetTimestampUnit(),单位是秒。
例如:你要发送8000赫兹的采样频率,你需要设置时间戳,由于它是每秒8000次增加,所以时间戳单元为1/8000,这也是这个库的默认设置。如果为44100赫兹,则如下:
sess.SetTimestampUnit(1.0/44100.0);
2 发送和接收数据
2.1 设置目的地址:
在你发送数据之前,必须指定包所要发送的目的地址。可使用的函数有AddDestination,DeleteDestination 和ClearDestinations。例如:
unsigned long addr =ntohl(inet_addr("127.0.0.1"));
sess.AddDestination(addr,5000);
既是将包发送到本机的5000端口上。Ntohl是将网络字节序转换为长整形。
2.2 发送RTP包
当已指定好包的目标地址后就可以发送了。调用成员函数SendPacket(),这是一个可以重载的函数,他有5种函数,可以指定发送的负载类型,标志符,时间戳增量,
如(其中的一种):
sess.SendPacket("1234567890",10,0,false,10);
参数1为发送的数据,2为数据长度,3为负载类型,4为标志符,5为时间戳增量。
如果你所要发送的数据的负载类型,标识符和时间戳增量是相同的,你可以使用另一种定义,
但是使用之前你必须为这些参数设置默认得值,课题通过调用成员函数SetDefaultPayloadType,SetDefaultMark 和SetDefaultTimeStampIncrement。这样你就可以在SendPacket()函数中不用设置后三个参数了。
sess.SetDefaultPayloadType(0);
sess.SetDefaultMark(false);
sess.SetDefaultTimeStampIncrement(10);
sess.SendPacket("1234567890",10);
2.3 接收数据包
首先你需要调用函数PollData(),这个函数是处理接受到的RTP和RTCP包,他会处理所有到达端口的包,因此你可以重复调用函数GotoFirstSource()和 GotoNextSource()来遍历所有的源,如果你只是对各个源所携带的数据有兴趣,你可以调用GotoFirstSourceWithData 和 GotoNextSourceWithData来获取接收到的各个源的数据,所有以上函数调用成功返回值为真,否则是false,当一个源正在被处理时,你可以调用函数GetNextPacket()来获取下一个RTP数据包,但是使用完必须delete掉。例子:
if(sess.GotoFirstSourceWithData())
{
do
{
RTPPacket *pack;
pack = sess.GetNextPacket();
// process packet
delete pack;
} while (sess.GotoNextSourceWithData());
}
更多关于RTPPacket的信息看手册。
2。4 接受方式
JRTPLIB 为 RTP 数据报定义了三种接收模式,其中每种接收模式都具体规定了哪些到达的 RTP 数据报将会被接受,而哪些到达的 RTP 数据报将会被拒绝。通过调用 RTPSession 类的 SetReceiveMode() 方法可以设置下列这些接收模式:
RECEIVEMODE_ALL 缺省的接收模式,所有到达的 RTP 数据报都将被接受;
RECEIVEMODE_IGNORESOME 除了某些特定的发送者之外,所有到达的 RTP 数据报都将被接受,而被拒绝的发送者列表可以通过调用 AddToIgnoreList()、DeleteFromIgnoreList() 和 ClearIgnoreList() 方法来进行设置;
RECEIVEMODE_ACCEPTSOME 除了某些特定的发送者之外,所有到达的 RTP 数据报都将被拒绝,而被接受的发送者列表可以通过调用 AddToAcceptList()、DeleteFromAcceptList 和 ClearAcceptList() 方法来进行设置。
2。5 多播
2。5。1 发送到一个多播组
要想加入一个多播组你不需要做个多的设定。只需要建立一个目的地指表或数组,RTP和RTCP包就会自动多播发送了。
唯一可能需要做的事情是设定数据包的Time ToLive(TTL) (生存时间),他决定了一个数据包的存在时间,位于IP 头部,可以是用函数SetMulticastTTL来设定。
2。5。2 从多播网络接收数据
你必须首先加入一个多播网络才可以重中接收数据。使用函数JoinMulticastGroup,LeaveMulticastGroup和LeaveAllMulticastGroups,他们是以IP为参数的。
2。6 关于接受和发送RTCP包
这个库可以很简单的处理RTCP信息,只要你正常调用PollData()函数,他会自动处理收到的RTCP信息,并对rtcp信息进行统计判断是否需要发送RR,而调用SendPacket()来决定什么时候发送RTCP 的SR包及其信息。
3 控制消息
3。1 自己的消息
通过调用 RTPSession 类提供的SetLocalName()、SetLocalEMail()、SetLocalLocation()、SetLocalPhone()、SetLocalTool() 和 SetLocalNote()方法,JRTPLIB 又允许程序员对RTP会话的控制信息进行设置。所有这些方法在调用时都带有两个参数,其中第一个参数是一个 char 型的指针,指向将要被设置的数据;而第二个参数则是一个 int 型的数值,表明该数据中的前面多少个字符将会被使用。例如下面的语句可以被用来设置控制信息中的电子邮件地址:
sess.SetLocalEMail("xiaowp@linuxgam.com",19);
在 RTP 会话过程中,不是所有的控制信息都需要被发送,通过调用 RTPSession 类提供的EnableSendName()、EnableSendEMail()、EnableSendLocation()、EnableSendPhone()、EnableSendTool() 和EnableSendNote() 方法,可以为当前 RTP 会话选择将被发送的控制信息。
3。2 其他消息
你可以通过函数GetCurrentSourceInfo() 获得当前消息源的信息,另外你可以首先获得源的SSRC,然后调用GetSourceInfo,参数为SSRC,获得源的信息。
4 句柄
Youcan specify handlers for certain types of events. For example: whena
source joins the session, when a source times out, whenSSRC identifiers
collide, ...
Formore information about the use of handlers you should take alook
atthe reference manual (manual.txt)
让JRTPLIB 发送/接收数据时使用同一个端口
这两周在做MPEG4视频传输时,想借助STUN协议穿透内网实现P2P,但是却发现JRTPLIB在发送数据和接收数据时使用的不是同一个PORT,这就可能导致无法穿透内网,我的想法大致如下:
假设A的RTP使用端口1000,则起对应的RTCP端口必定为1001,在初始化RTP信令后,JRTPLIB会随机使用另外一个端口3333来发送RTP数据,1000端口是用来接收RTP数据的,1001接收RTCP包。
假设B的RTP使用端口2000,则起对应的RTCP端口必定为2001,在初始化RTP信令后,JRTPLIB会随机使用另外一个端口4444来发送RTP数据,2000端口是用来接收RTP数据的,2001接收RTCP包。
又假设A,B均在NAT后,经NAT转换后,利用STUN协议,A开始向B发包,B也向A发包,由于NAT的存在,B发给A的包并不是A发出去的目的地址端口发过来的,目的端口只受数据不发数据(A将数据包发给B的RTP接收端口后,NAT A就只接收B的这个RTP端口发过来的数据,对于B用来发送数据的端口而言,是不请自到的),因此NAT A会丢弃B发过来的包,同理NAT B也会丢弃A发过来的包。
这样一来,为了能UDP PUNCH HOLE,A,B就必须再往对方用来发送数据的端口打一个洞,以便欺骗NAT,但UDP是不可靠的,而NAT影射也有时间限制,要保证NAT的影射关系不变就的定时打洞给对方,个人感觉这种方法不是很好。
经过查看JRTPLIB源码,它的Create()函数里是这么写的
addr.sin_family = AF_INET;
addr.sin_port = htons(0); //就是这里使用随机端口
addr.sin_addr.s_addr = htonl(0);
if (bind(sendsock,(struct sockaddr*)&addr,sizeof(struct sockaddr)) != 0)
{
return ERR_RTP_CANTBINDSOCKET;
}
socklen = sizeof(struct sockaddr_in);
if (getsockname(sendsock,(struct sockaddr*)&addr,&socklen) != 0)
{
return ERR_RTP_CANTGETSOCKETPORT;
}
sendport = ntohs(addr.sin_port);//这里是发送数据的端口
很明显,它用的是随机端口,BIND成功后再查询这个端口的,在RTPSession里用于获取发送端口的函数GetSendPort()直接返回了这个sendport,如下:
int GetSendPort()
{
return sendport;
}
为此,我只好修改JRTPLIB V2.9的源码了,在rtpconnection.cpp里,将发送数据的sendto语句里的socket都换成rtpsocket,让他使用bind在rtp端口的socket即可,同时删除sendsocket,并将GetSendPort()直接返回portbase。经过修改,发送/接收数据时就只有RTP和RTCP端口了,而且收发数据都通过RTP绑定的那个端口进行。