jrtplib-3.7.1笔记--流程一遍

一、RTP 是进行实时流媒体传输的标准协议和关键技术

实时传输协议(Real-time Transport ProtocolPRT)是在 Internet 上处理多媒体数据流的一种网络协议,利用它能够在一对一(unicast,单播)或者一对多(multicast,多播)的网络环境中实现传流媒体数据的实时传输。RTP 通常使用 UDP 来进行多媒体数据的传输,但如果需要的话可以使用 TCP 或者 ATM 等其它协议。
 
协议分析 每一个RTP数据报都由头部(Header)和负载(Payload)两个部分组成,其中头部前 12 个字节的含义是固定的,而负载则可以是音频或者视频数据。

RTP 
是目前解决流媒体实时传输问题的最好办法,要在 Linux 平台上进行实时传送编程,可以考虑使用一些开放源代码的 RTP 库, LIBRTPJRTPLIB JRTPLIB 是一个面向对象的 RTP 库,它完全遵循 RFC 1889 设计,在很多场合下是一个非常不错的选择。JRTPLIB 是一个用 C++ 语言实现的 RTP 库,这个库使用socket 机制实现网络通讯 因此可以运行在 WindowsLinuxFreeBSDSolarisUnixVxWorks 等多种操作系统上。

嵌入式版的环境搭建

嵌入式版上的环境搭建和PC机上有些不同,如不注意可能导致两个库都不能使用。

首先,必须先安装jthread库,再安装jrtplib库;其次,要交叉编译,需修改configure文件。具体步骤为:

[root@linuxgam src]# cd jthread-1.2.1

[root@linuxgam jthread-1.2.1]# ./configure  -host=arm-linux  –prefix=/usr/local/arm/2.95.3

 

[root@linuxgam src]# cd jrtplib-3.7.1

[root@linuxgam jrtplib-3.7.1]# ./configure  -host=arm-linux  -prefix=/usr/local/arm/2.95.3

 

二、JRTPLIB 库的使用方法及程序实现
a在使用 JRTPLIB 进行实时流媒体数据传输之前,首先应该生成 RTPSession 类的一个实例来表示此次 RTP 会话,然后调用 Create() 方法来对其进行初始化操作。RTPSession 类的 Create() 方法只有一个参数,用来指明此次 RTP 会话所采用的端口号。
 
RTPSession sess;

RTPUDPv4TransmissionParams transparams;

RTPSessionParams sessparams;


// 创建RTP会话 sessparams.SetAcceptOwnPackets(true); transparams.SetPortbase(portbase); status = sess.Create(sessparams, &transparams); checkerror(status);

//函数原型 jrtplib-3.7.1/src/rtpsession.cpp(151) int RTPSession::Create(const RTPSessionParams &sessparams, RTPTransmitter *transmitter) { int status; if (created) return ERR_RTP_SESSION_ALREADYCREATED; usingpollthread = sessparams.IsUsingPollThread(); useSR_BYEifpossible = sessparams.GetSenderReportForBYE(); sentpackets = false; // Check max packet size if ((maxpacksize = sessparams.GetMaximumPacketSize()) < RTP_MINPACKETSIZE) return ERR_RTP_SESSION_MAXPACKETSIZETOOSMALL; rtptrans = transmitter; if ((status = rtptrans->SetMaximumPacketSize(maxpacksize)) < 0) return status; deletetransmitter = false; return InternalCreate(sessparams); } 

b设置恰当的时戳单元,是 RTP 会话初始化过程所要进行的另外一项重要工作,这是通过调用 RTPSession 类的 SetTimestampUnit() 方法来实现的,该方法同样也只有一个参数,表示的是以秒为单元的时戳单元。

sessparams.SetOwnTimestampUnit(1.0/8000.0);

int RTPSession::SetTimestampUnit(double u) { if (!created) return ERR_RTP_SESSION_NOTCREATED; int status; BUILDER_LOCK status = rtcpbuilder.SetTimestampUnit(u); BUILDER_UNLOCK return status; }
c RTP 会话成功建立起来之后,接下去就可以开始进行流媒体数据的实时传输了。首先需要设置好数据发送的目标地址,RTP 协议允许同一会话存在多个目标地址,这可以通过调用 RTPSession 类的 AddDestination()DeleteDestination()  ClearDestinations() 方法来完成。

 

// 指定RTP数据接收端

uint16_t portbase, destport;

uint32_t destip;

RTPIPv4Address addr(destip, destport);

status = sess.AddDestination(addr);

checkerror(status);

 

d、对于同一个RTP会话来讲,负载类型、标识和时戳增量通常来讲都是相同的,JRTPLIB允许将它们设置为会话的默认参数,这是通过调用 RTPSession类的SetDefaultPayloadType()SetDefaultMark() SetDefaultTimeStampIncrement()方法来完成的。为RTP会话设置这些默认参数的好处是可以简化数据的发送,例如,如果为 RTP会话设置了默认参数:

 

// 设置RTP会话默认参数

sess.SetDefaultPayloadType(96);

sess.SetDefaultMark(false);

sess.SetDefaultTimestampIncrement(160);

//函数原型 jrtplib-3.7.1/src/rtppacketbuilder.h(215) inline int RTPPacketBuilder::SetDefaultPayloadType(uint8_t pt) { if (!init) return ERR_RTP_PACKBUILD_NOTINIT; defptset = true; defaultpayloadtype = pt; return 0; } inline int RTPPacketBuilder::SetDefaultMark(bool m) { if (!init) return ERR_RTP_PACKBUILD_NOTINIT; defmarkset = true; defaultmark = m; return 0; } inline int RTPPacketBuilder::SetDefaultTimestampIncrement(uint32_t timestampinc) { if (!init) return ERR_RTP_PACKBUILD_NOTINIT; deftsset = true; defaulttimestampinc = timestampinc; return 0; } 

 

e、之后就可以调用 RTPSession 类的 SendPacket() 方法向所有的目标地址发送流媒体数据,进行数据发送时只需指明要发送的数据及其长度就可以了(SendPacket()最典型的用法是类似于下面的语句,其中第一个参数是要被发送的数据,而第二个参数则指明将要发送数据的长度,再往后依次RTP负载类型标识时戳增量至少要用到前两个参数):

 

status = sess.SendPacket(send_buf, sizeof(send_buf), 0, false, 10);

checkerror(status);

函数原型 jrtplib-3.7.1/src/rtpsession.cpp(552) int RTPSession::SendPacket(const void *data,size_t len) { int status; if (!created) return ERR_RTP_SESSION_NOTCREATED; BUILDER_LOCK if ((status = packetbuilder.BuildPacket(data,len)) < 0) { BUILDER_UNLOCK return status; } if ((status = rtptrans->SendRTPData(packetbuilder.GetPacket(),packetbuilder.GetPacketLength())) < 0) { BUILDER_UNLOCK return status; } BUILDER_UNLOCK SOURCES_LOCK sources.SentRTPPacket(); SOURCES_UNLOCK PACKSENT_LOCK sentpackets = true; PACKSENT_UNLOCK return 0; } int RTPSession::SendPacket(const void *data,size_t len, uint8_t pt,bool mark,uint32_t timestampinc) { int status; if (!created) return ERR_RTP_SESSION_NOTCREATED; BUILDER_LOCK if ((status = packetbuilder.BuildPacket(data,len,pt,mark,timestampinc)) < 0) { BUILDER_UNLOCK return status; } if ((status = rtptrans->SendRTPData(packetbuilder.GetPacket(),packetbuilder.GetPacketLength())) < 0) { BUILDER_UNLOCK return status; } BUILDER_UNLOCK SOURCES_LOCK sources.SentRTPPacket(); SOURCES_UNLOCK PACKSENT_LOCK sentpackets = true; PACKSENT_UNLOCK return 0; } int RTPSession::SendPacketEx(const void *data,size_t len, uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords) { int status; if (!created) return ERR_RTP_SESSION_NOTCREATED; BUILDER_LOCK if ((status = packetbuilder.BuildPacketEx(data,len,hdrextID,hdrextdata,numhdrextwords)) < 0) { BUILDER_UNLOCK return status; } if ((status = rtptrans->SendRTPData(packetbuilder.GetPacket(),packetbuilder.GetPacketLength())) < 0) { BUILDER_UNLOCK return status; } BUILDER_UNLOCK SOURCES_LOCK sources.SentRTPPacket(); SOURCES_UNLOCK PACKSENT_LOCK sentpackets = true; PACKSENT_UNLOCK return 0; } int RTPSession::SendPacketEx(const void *data,size_t len, uint8_t pt,bool mark,uint32_t timestampinc, uint16_t hdrextID,const void *hdrextdata,size_t numhdrextwords) { int status; if (!created) return ERR_RTP_SESSION_NOTCREATED; BUILDER_LOCK if ((status = packetbuilder.BuildPacketEx(data,len,pt,mark,timestampinc,hdrextID,hdrextdata,numhdrextwords)) < 0) { BUILDER_UNLOCK return status; } if ((status = rtptrans->SendRTPData(packetbuilder.GetPacket(),packetbuilder.GetPacketLength())) < 0) { BUILDER_UNLOCK return status; } BUILDER_UNLOCK SOURCES_LOCK sources.SentRTPPacket(); SOURCES_UNLOCK PACKSENT_LOCK sentpackets = true; PACKSENT_UNLOCK return 0; } 

f、接收实时流媒体数据,同 一个RTP 会话中允许有多个参与者( 源),通 过调用GotoFirstSourceWithData()和 GotoNextSourceWithData()函数来实现源的遍历,通过 GetNextPacket()函数提取 RTP 数据包。数据包接受以RTPSession的成员函数BeginDataAccess开始、EndDataAccess结束,BeginDataAccess确保轮询(poll)线程不会在这期间访问source table 。EndDataAccess 调用完成后,轮询(poll)线程会得到锁而继续访问。

sess.BeginDataAccess();

 

// check incoming packets if (sess.GotoFirstSourceWithData()) { do { RTPPacket *pack; uint8_t *data; size_t length; while ((pack = sess.GetNextPacket()) != NULL) { data = pack->GetPayloadData(); length = pack->GetPayloadLength(); printf("Got %d-%d: %d/%d:%s Length:%d/n", portbase, destport, i, num, data, length); sess.DeletePacket(pack); } } while (sess.GotoNextSourceWithData()); } sess.EndDataAccess();

 

下面的函数要在BeginDataAccess 和EndDataAccess之间被调用:

int RTPSession::BeginDataAccess() {

if (!created)

return ERR_RTP_SESSION_NOTCREATED;

SOURCES_LOCK

return 0;

}

int RTPSession::EndDataAccess() {

if (!created)

return ERR_RTP_SESSION_NOTCREATED;

SOURCES_UNLOCK

return 0;

}

 

/////////////////////////////////////////////////////////////////////////////////////////Start

• bool GotoFirstSource()
      开始递归参与者的第一个流,如果找到了,就返回tree,否则返回false。ps:我们通过这个函数和下面的GotoNextSource遍历source table中的每一个source。
      bool RTPSources::GotoFirstSource(){

sourcelist.GotoFirstElement();

if (sourcelist.HasCurrentElement())

return true;

return false;

}

• bool GotoNextSource()
      设置当前的源(source)为source table中的下一个源。如果已经到尾部了就返回false.

bool RTPSources::GotoNextSource(){

sourcelist.GotoNextElement();

if (sourcelist.HasCurrentElement())

return true;

return false;

}

• bool GotoPreviousSource()
      设置当前的源(source)为source table中上一个源。如果已经到头部了就返回false.
bool RTPSources::GotoPreviousSource(){ sourcelist.GotoPreviousElement(); if (sourcelist.HasCurrentElement()) return true; return false; }
• bool GotoFirstSourceWithData()
      开始递归参与者中第一个有RTP数据的流,如果找到了,就返回tree,否则返回false。PS:在接收数据是我们常用的是这套函数,因为如果没有数据要来都没用。

bool RTPSources::GotoFirstSourceWithData(){

bool found = false;


sourcelist.GotoFirstElement();

while (!found && sourcelist.HasCurrentElement()){

RTPInternalSourceData *srcdat;


srcdat = sourcelist.GetCurrentElement();

if (srcdat->HasData())

found = true;

else

sourcelist.GotoNextElement();

}


return found;

}

• bool GotoNextSourceWithData()
      设置当前的源(source)为source table中有RTP数据的下一个源。如果已经到尾部了就返回false.

bool RTPSources::GotoNextSourceWithData(){

bool found = false;


sourcelist.GotoNextElement();

while (!found && sourcelist.HasCurrentElement()){

RTPInternalSourceData *srcdat;


srcdat = sourcelist.GetCurrentElement();

if (srcdat->HasData())

found = true;

else

sourcelist.GotoNextElement();

}


return found;

}

• bool GotoPreviousSourceWithData()
      设置当前的源(source)为source table中有RTP数据的上一个源。如果已经到头部了就返回false.

bool RTPSources::GotoPreviousSourceWithData(){

bool found = false;


sourcelist.GotoPreviousElement();

while (!found && sourcelist.HasCurrentElement()){

RTPInternalSourceData *srcdat;


srcdat = sourcelist.GetCurrentElement();

if (srcdat->HasData())

found = true;

else

sourcelist.GotoNextElement();

}


return found;

}

• RTPSourceData *GetCurrentSourceInfo()
      返回当前参与者的当前源(source)的RTPSourceData 实列。ps:返回的这个RTPSourceData 就是本进程从期它参与者的RTCP数据包中收集得到的信息,对我们来说其实很有用,只是作者的例程没有用上,国内的网络也没有提到。在RFC3550中有关RTCP的东西都在这了,看过RFC3550的人都知到,里头谈得最多的就是RTCP。这个类我们以后会专门说。

RTPSourceData *RTPSources::GetCurrentSourceInfo(){

if (!sourcelist.HasCurrentElement())

return 0;

return sourcelist.GetCurrentElement();

}     

• RTPSourceData *GetSourceInfo(uint32 t ssrc)
      返回由ssrc指定的RTPSourceData ,或都NULL(当这个条目不存在)。ps:这个函数也很有用。因为GetCurrentSourceInfo只有在GotoFirstSource等上下文当中才能用。如果我们是在RTPSource子类的成员函数中,我们没有这个上下文,就只能用这个函数。

RTPSourceData *RTPSources::GetSourceInfo(uint32_t ssrc){

if (sourcelist.GotoElement(ssrc) < 0)

return 0;

if (!sourcelist.HasCurrentElement())

return 0;

return sourcelist.GetCurrentElement();

• RTPPacket *GetNextPacket()
      得到当前参与者当前媒体流的下一个RTP数据包。

inline RTPPacket *RTPSourceData::GetNextPacket() { if (!validated) return 0; RTPPacket *p; if (packetlist.empty()) return 0; p = *(packetlist.begin()); packetlist.pop_front(); return p; } ///// RTPPacket *RTPSources::GetNextPacket() { if (!sourcelist.HasCurrentElement()) return 0; RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); RTPPacket *pack = srcdat->GetNextPacket(); return pack; } // RTPRawPacket *RTPUDPv4Transmitter::GetNextPacket() { if (!init) return 0; MAINMUTEX_LOCK RTPRawPacket *p; if (!created) { MAINMUTEX_UNLOCK return 0; } if (rawpacketlist.empty()) { MAINMUTEX_UNLOCK return 0; } p = *(rawpacketlist.begin()); rawpacketlist.pop_front(); MAINMUTEX_UNLOCK return p; } // RTPRawPacket *RTPUDPv6Transmitter::GetNextPacket() { if (!init) return 0; MAINMUTEX_LOCK RTPRawPacket *p; if (!created) { MAINMUTEX_UNLOCK return 0; } if (rawpacketlist.empty()) { MAINMUTEX_UNLOCK return 0; } p = *(rawpacketlist.begin()); rawpacketlist.pop_front(); MAINMUTEX_UNLOCK return p; } 

/////////////////////////////////////////////////////////////////////////////////////////End

g、数据传输结束,释放资源

//释放资源最多等待十秒超时,然后释放所有占有资源

sess.BYEDestroy(RTPTime(10,0),0,0); 


三、上面代码和媒体流没关,就是example里就发发一些数字把JRTPLIB测试过而已。

你可能感兴趣的:(session,网络协议,tree,table,嵌入式,FreeBSD)