1 从RTPSession类开始
所有的类和函数都是jrtplib名字空间的一部分,为了简化代码,我们声明我们使用的是如下的名字空间:
using namespace jrtplib;
为了使用RTP,首先要创建一个RTPSession对象.这个构造函数接受两个参数:一个RTPRandom类的实例,一个RTPMemoryManager类的实例.现在,我们使用最简单的默认的设置,如下:
RTPSession session;
1.1 创建Session
调用带有三个参数的Create函数.
参数1
RTPSessionParams类型.指定这个session的大概的选项.这个类里面的一个参数必须要设置好,否则session就会创建失败.就是你要发送的数据的时间戳的单位,这个参数可以用一个时间段内的采样数去除以这个时间段的长度.因此,假设我们要发送一个8000Hz的语音数据,可以用如下的代码:
RTPSessionParams sessionParams; sessionParams.SetOwnTimestampUnit(1.0/8000.0);
参数2
这个参数就真的取决于你想要一起用的RTP profile.
它是一个指向RTPTransmissionParams实例的指针,并且为传输部分描述参数.
参数3
选择要使用的传输组件,默认情况下,使用UDP通过IPV4的transmitter,并且对于这个特定的transmitter,那么参数2中的传输参数应该设为RTPUDPv4TransmissionParams类型的.因此,假设我们使用8000端口,我们可以使用如下的代码:
RTPUDPv4TransmissionParams transparams; transparams.SetPortbase(8000);
现在我们可以调用RTPSession类的Create成员函数,代码如下:
int status = session.Create(sessionparams,&transparams); if (status < 0) { std::cerr << RTPGetErrorString(status) << std::endl; exit(-1); }
如果Create函数出错,那么返回一个负值,它指出出错了.可以用RTPGetErrorString()函数来获得.
当session创建成功了,此时,要设置RTP和RTCP数据要发送的目的地.这是通过调用RTPSession的成员函数AddDestination来实现的.这个函数带一个RTPAddress类型的参数,RTPAddress是一个抽象类,对于UDP通过IPV4的transmitter,真正的子类是RTPIPv4Address.假设把数据向运行在9000端口的进程发送,那么代码如下:
uint8_t localip[]={127,0,0,1}; RTPIPv4Address addr(localip,9000); status = session.AddDestination(addr); if (status < 0) { std::cerr << RTPGetErrorString(status) << std::endl; exit(-1); }
如果这个库是用JThread支持编译的,那么收到的数据是在后台处理的.以下两种情况:
A JThread支持没有在编译时使能
B 在session参数中指定不使用poll线程
下,必须使用RTPSession的成员函数Poll来处理到达的数据并且必要时发送RTCP数据.
现在,我们先假定我们使能了poll线程.
假设在一分钟内,我们想要发送包含20ms(160个采样)的silence,并且我们想要当一个从其他地方来的包被接收的时候能够被指出来.同样假定我们有L8数据,并且要使用负荷类型为96.首先,我们将要设定一些默认值:
session.SetDefaultPayloadType(96); session.SetDefaultMark(false); session.SetDefaultTimestampIncrement(160);
下一步,我们要创建包含160个silence采样的缓冲区,并且创建一个表示20ms的RTPTime实例.我们也保存当前时间,这样我们可以知道何时1分钟已经走完了.
uint8_t silencebuffer[160]; for (int i = 0 ; i < 160 ; i++) silencebuffer[i] = 128; RTPTime delay(0.020); RTPTime starttime = RTPTime::CurrentTime();
下一步,是主循环.这个循环中,要发送一个包含160字节的负载数据.然后,数据处理开始进行,这个稍后阐述.最终,我们等待20ms并检测是否60s已经过去.
bool done = false; while (!done) { status = session.SendPacket(silencebuffer,160); if (status < 0) { std::cerr << RTPGetErrorString(status) << std::endl; exit(-1); } // // Inspect incoming data here // RTPTime::Wait(delay); RTPTime t = RTPTime::CurrentTime(); t -= starttime; if (t > RTPTime(60.0)) done = true; }
关于会话参与者的信息,以及获得包等的信息,都必须在调用成员函数BeginDataAccess和EndDataAccess之间完成.这样可以保证后台线程不会在你在访问数据的时候,同时改变你的数据.我们逐个访问会话的参与者通过GotoFirstSource和GotoNextSource成员函数.从当前选中的参与者中取得数据包,可以通过成员函数GetNextPacket,这个函数可以返回一个指向RTPPacket类的实例的指针.当你不再需要这个packet的时候,你要delete它.处理到达的数据的过程可以如下:
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();
当前选中的源的信息可以通过GetCurrentSourceInfo成员函数RTPSession类的来获取.这个函数返回一个指针指向RTPSourceData的指针,这个对象包括了所有的关于source的信息:从那个源来的sender reports,receiver reports,SDES信息等等.
当主循环结束的时候,我们发送一个BYE包来告诉其他参与者我们的分离,并且清理RTPSession类.同样我们需要等最多10s来让BYE包被发送出去,否则,我们只是简单地离开会话,而没有发送BYE包.
delay = RTPTime(10.0); session.BYEDestroy(delay,"Time's up",9);
2 错误码
除非特地指定的,否则都是0或者正返回值表示成功,负值表示出错.可以用RTPGetErrorString获取.
3 内存管理
可以通过继承RTPMemoryManager来写自己的内存管理类.下面是一个简化的实现代码:
class MyMemoryManager : public RTPMemoryManager { public: MyMemoryManager() { } ~MyMemoryManager() { } void *AllocateBuffer(size_t numbytes, int memtype) { return malloc(numbytes); } void FreeBuffer(void *p) { free(p); } };
在RTPSession类的构造函数中,可以指定想要使用这个内存管理:
MyMemoryManager mgr; RTPSession session(0, &mgr);
此时,所有的内存分配和声明都可以通过使用mgr的AllocateBuffer和FreeBuffer来完成.