可以对照jrtplib样例1和2看以下内容。
一、创建一个RTP对话的步骤:
1.使用RTPSession类创建一个会话对象sess/session。
2.通过RTP会话的参数类RTPSessionParams创建一个参数设置的对象sessparams/sessionparams。具体设置的属性有:时间戳单元(SetOwnTimestampUnit),是否允许接受自己的数据(SetAcceptOwnPackets)。
3.传递参数类(RTPTransmissionParams)下面的有基于UDP和IPV4的传递参数的派生类(RTPUDPv4TransmissionParams),除此之外,还有基于UDP和IPV6的派生类,一般就使用头第一个了。用于设置本机的数据传递参数,主要是RTP数据包中的首部参数,本机要使用的端口号(偶数)。
4.通过会话对象sess/session的Create方法调用3和4中的两个类完成对话的创建。
5.在IPV4目标地址类(RTPIPv4Address)创建的时候,完成目标端口号和目标ip地址。
6.通过会话对象sess/session的方法AddDestination()将创建的目标地址对象添加到发送到队列中,这里可以添加多个目标地址。
7.以上就是使用jrtplib包进行TCP通信的通用步骤。
说明:
1.端口号选择偶数是因为在jrtplib中偶数位用于RTP通信,然后自动将下一数字(奇数)用于RTCP通信。
2.创建RTP会话依靠RTPSession类的方法,其他的三个类RTPSessionParams、RTPTransmissionParams、RTPIPv4Address作为RTPSession的参数分别为其提供RTP会话所需要的时间戳单元、本机的传递参数设置、以及目标主机的IP地址和端口号,样例中一般先创建前两个类,完成RTP对话创建,最后使用第三个类添加目标主机的地址,也可以添加多个目标主机,就是发往不同的主机上。
二、实现RTP数据的发送与接受
发送数据:
数据的发送由一个重载函数完成
1. intjrtplib::RTPSession::SendPacket(const void* data,size_t len)
2. intjrtplib::RTPSession::SendPacket(const void* data,size_t len,uint8_tpt,bool mark,uint32_t timetampinc)
第二个函数相对于第一个函数多了pt(p填充),M标志位,以及时间戳,这几个参数都是RTP首部包中需要的部分。下图是RTP包的首部格式,不太明白的可以看看相关RTP包的介绍,红色标注对应这三个参数,其他的暂时不用关心。
如果需要使用第一个函数发送数据,需要设置先将这三个参数设置成默认值。具体设置通过RTP会话对象的三个属性完成。
session.SetDefaultPayloadType(96);
session.SetDefaultMark(false);
session.SetDefaultTimestampIncrement(160);
重要说明:
在创建一个TCP对话和发送数据中两次用到了与时间戳相关的参数(蓝色处),这里解释一下这两处分别代表什么意思,该如何设置。
第一处是RTP会话的参数类RTPSessionParams,这里需要设置基本的时钟单元,例如,一个8000hz的声音信号,接受端每接收一个采样信号的时间就是要增加1/8000s。这个就是传递这个信号最基本的时钟单元。sessionparams.SetOwnTimestampUnit(1.0/8000.0);再例如:一个20帧的图像,每传送一幅图像过去,时钟信号需要增加1/20s.
第二处是RTP会话对象的默认时间戳属性(蓝色标记处),假设我们一个每一个RTP包中包含一个20ms的声音信号(对于声音而言,每一个字节就是一个采样信号),则这一个包传送过去,时间需要增加多久呢?0.02s/(1/8000s)=160。得到的160就是以第一处设置的基本时钟单元为单位的传送这20ms的声音信号所需要的时间。
最后,我们再看第一处设置的一定要是(1.0/8000.0)吗?答案应该是否定的,第一处增加或者减少几倍,对应的第二处增加或者减少几倍,只要能够保证第二处的时钟单元传送所需的时间正确就可以(猜测,还未证实,应该是这样的)。
接收数据:
接收端首选要使用RTPSession的成员函数poll接受来自不同会话参与者的数据(不同的IP地址发来的数据,这里称之为会话参与者)。
在数据接受端,关于RTP会话参与者以及数据包检索等信息,可以在对RTPSession类的成员函数RTPSession::BegainDataAccess和RTPSession::EndDataAceess的调用之间完成。这种方式是为了保证你所使用的数据不会被其他隐藏线程所调用。使用RTPSession::GotoFirstSource和RTPSession::GotoNextSource两个成员函数的迭代找出你所需要的会话参与者,当前被选中的会话参与者的数据包可以通过RTPSession::GetNextPacket函数获取,该函数返回一个RTPPacket类的对象。这是本文中提到来的第五个类(三个与参数RTP会话的参数设置有关,一个是贯穿始终的RTP会话类(RTPSession)。这个是用于数据接收的类RTPPacket)这个包文件中所有数据(包括RTP首部的各种信息、以及负载数据)都可以通过该类的成员函数得到。
例子:
status=session.Poll();
check(status);
status=session.BeginDataAccess();
if (session.GotoFirstSource())
{
do
{
RTPPacket *packet;
while ((packet = session.GetNextPacket()) != 0)
{
std::cout << "Got packet with extended sequence number "
<< packet->GetExtendedSequenceNumber()
<< " from SSRC " << packet->GetSSRC()
<< std::endl;
session.DeletePacket(packet);
}
} while (session.GotoNextSource());
}
session.EndDataAccess();
再次说明
1.RTPSession::GetNextPacket函数是获取当前参与者的数据包,而不是下一个
2.packet->GetPayloadData()用来获取负载数据
3.除此之外,如果想要获取发送报告,接受报告,SDES等数据可以通过RTPSsion类的成员函数GetCurrentSourceInfo函数来获取。这个函数返回的是一个RTPSourceData类。
4.文中所说的会话参与者意思是:数据接受端接受不止一个IP主机发送过来的数据,不同的参与者指的就是不同IP主机。
三、与时间相关的几个函数的解释
与时间相关的类是RTPTime类。其功能如下
1、构造函数
jrtplib::RTPTime::RTPTime(double t)//以s为单位
jrtplib::RTPTime::RTPTime(int64_t seconds, uint32_t microseconds )//以(s,ms)为单位。
通过RTPTime构造一个延时类
例如:RTPTimedelay(0.02)。这个时候并没有延时,只是构造了一个需要延时0.02s的dealy对象。
2、成员函数
(1) RTPTime jrtplib::RTPTime::CurrentTime() 获取当前的时间,这个时间是以s为单位,从世界标准时间的1970年1月1日00:00:00开始算起为第0s。
例如:RTPTimestarttime =RTPTime::CurrentTime();构造一个RTPTime的对象,并且对其进行类的成员函数进行操作,等同于
RTPTime starttime;
starttime.CurrentTime();
(2)void jrtplib::RTPTime::Wait ( constRTPTime & delay) 等待延时,传入的是“1、构造函数”中的对象。
RTPTime::Wait(delay);
四、资源释放
当程序结束的时候,需要发送一个再见包,用来释放RTP会话资源。
delay= RTPTime(10.0);
session.BYEDestroy(delay,"Time'sup",9);
五、RTP发送与接受的样例
样例下载地址:
https://git.coding.net/DavidZh666/Video_TCP_h264.git
该样例通过jrtplib中的example2.cpp修改后完成。
在Ubuntu中,使用如下命令编译完成
g++ jrtp_send.cpp -ojrtp_send -ljrtp
g++ jrtp_receive.cpp-o jrtp_receive -ljrtp
运行
./jrtp_receive.cpp
./jrtp_send.cpp