LIVE555研究之五:RTPServer(二)

接上文,main函数的几行代码创建了RTSPServer类的子类DynamicRTSPServer对象。RTPServer类是server类的基类。DynamicRTSPServer代表详细的server子类。我们今天介绍的server程序就是基于该类实现的。

在创建DynamicRTSPServer时传入了值为554的port号。这是由于RTSP默认port号为554,与http默认使用80port是一样的。

DynamicRTSPServer

继承关系:

Medium是非常多类的基类。内部定义了指向环境类的引用和一个char类型媒体名称。并定义了依照媒体名称,查找相应媒体的成员函数lookupByName。

由于MediaSink、MediaSouce、MediaSession、RTSPClient、RTPServer均继承自该类。因此在Medium中定义了非常多推断该类是哪个媒体类型的函数:

  virtual Boolean isSource() const;

  virtual Boolean isSink() const;

  virtual Boolean isRTCPInstance() const;

  virtual Boolean isRTSPClient() const;

  virtual Boolean isRTSPServer() const;

  virtual Boolean isMediaSession() const;

  virtual Boolean isServerMediaSession() const;

  virtual Boolean isDarwinInjector() const;

Medium中的实现均是返回false。在相应的子类中均会重定义相应函数。并返回true。

TaskToken fNextTask用来保存延迟任务的ID。

保存的任务ID用于被又一次调度。或者在该媒体对象被销毁时从延迟队列中取消调度。

RTPServer类是server类的基类。代表了server对象。在整个server执行期间,该对象一直存在。

定义了下面成员变量:

  HashTable* fServerMediaSessions; 

  HashTable* fClientConnections; 

  HashTable* fClientConnectionsForHTTPTunneling;   

  HashTable* fClientSessions; 

  HashTable* fPendingRegisterRequests;

从其成员变量能够看到RTPServer中维护了ServerMediaSession对象、ClientConnection、ClientSession对象的HashTable。

ServerMediaSessionSession相应server端一个媒体文件。当client请求多个媒体文件时,RTPServer内会维护相应的多个ServerMediaSession对象。ServerMediaSession对象通过媒体文件名称进行标识,如client请求a.264文件。则server就会在保存ServerMediaSession的HashTable中搜索相应文件名称为a.264的ServerMediaSession。如未找到,则说明还未为该媒体文件创建相应的ServerMediaSession。并创建一个新的ServerMediaSession与媒体文件名称关联后加入到HashTable。

lookupServerMediaSession用于在map中搜索相应媒体文件名称相应的ServerMediaSession。

void addServerMediaSession(ServerMediaSession* serverMediaSession);

  virtual ServerMediaSession* lookupServerMediaSession(char const* streamName);

  void removeServerMediaSession(ServerMediaSession* serverMediaSession);

  void removeServerMediaSession(char const* streamName);

以上三个成员函数分别用来加入、查询和删除相应ServerMediaSession项。

removeServerMediaSession被调用后。在RTPServer中维护的fServerMediaSession的HashTable中。该ServerMediaSession会被删除。可是相应的ServerMediaSession对象并不一定会被释放。

由于此时其它client还有可能在使用该媒体文件。仅仅有当其它client都释放了对该媒体文件的引用后,该对象才会被释放。

closeAllClientSessionsForServerMediaSession用于删除全部client对某一个媒体文件的引用。

deleteServerMediaSession在从fServerMediaSession中删除相应项目时同一时候也会删除全部client的引用,此后该对象的引用计数为0能够被安全释放。

在removeServerMediaSession时会检查引用计数,仅仅有当引用计数为0时该对象才会被释放。

if (serverMediaSession->referenceCount() == 0) //仅仅有当引入计数为0时才会被释放
{
Medium::close(serverMediaSession);
}
else
{
serverMediaSession->deleteWhenUnreferenced() = True;
}

ClientConnection对象

ClientConnection对象定义在RTPServer内部,为其内部类。

主要用于和client的通信。当有新的client连接到server时,会新建ClientConnection对象。其内部定义了发送、接收socket以及发送和接收缓冲区,并对client的命令进行处理和回应。

void handleRequestBytes(int newBytesRead);

用于处理client命令,在对RTSP命令进行分析后。提取出各种信息。然后进行分流处理。

对于OPTIONS、DESCRIBE、命令不支持、命令有误等其它错误命令的响应会直接在ClientConnection中进行处理。

而对于SETUP、PLAY、PAUSE、TERARDOWN等命令会传递到ClientSession中进行处理。

下面为分流代码:

 else if (strcmp(cmdName, "TEARDOWN") == 0
|| strcmp(cmdName, "PLAY") == 0
|| strcmp(cmdName, "PAUSE") == 0
|| strcmp(cmdName, "GET_PARAMETER") == 0
|| strcmp(cmdName, "SET_PARAMETER") == 0)
{
if (clientSession != NULL) { clientSession->handleCmd_withinSession(this, cmdName, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer); } else
{
handleCmd_sessionNotFound();
}

ClientSession对象会在client请求SETUP命令时在ClientConnection中创建。并分配一个ClientSessionID。

对于SETUP之前和对一些出错处理命令会在ClientConnection中进行响应。

ClientConnection维护了RTPServer的指针,能够在新建ClientSession对象后将其增加到RTPServer维护的fClientSessions中。

ClientSession中定义的成员:

RTSPServer& fOurServer;

 u_int32_t fOurSessionId;

ServerMediaSession* fOurServerMediaSession;

 

ClientSession也维护了对RTPServer的引用。同一时候也保存了指向ServerMediaSession的指针。在对SETUP的响应中。有这样一句话:

  if (fOurServerMediaSession == NULL)
{
// We're accessing the "ServerMediaSession" for the first time.
fOurServerMediaSession = sms;
fOurServerMediaSession->incrementReferenceCount();
}
else if (sms != fOurServerMediaSession)
{
// The client asked for a stream that's different from the one originally requested for this stream id. Bad request:
ourClientConnection->handleCmd_bad();
break;
}

由此我们知道依照眼下的实现。每一个clientSession仅仅能相应一个ServerMediaSession。即每一个client仅仅能请求一个媒体文件,不能同一时候请求两个媒体文件。假设须要同一时候支持多个媒体文件,就须要在ClientSession中维护一个ServerMediaSession集合。

ClientSession的noteLiveness用于client保活。

其内部实现例如以下:
void RTSPServer::RTSPClientSession::noteLiveness()

{
if (fOurServer.fReclamationTestSeconds > 0)
{
envir().taskScheduler()
.rescheduleDelayedTask(fLivenessCheckTask,
fOurServer.fReclamationTestSeconds*1000000,
(TaskFunc*)livenessTimeoutTask, this);
}
}

上述代码向调度器请求又一次调度一个延迟任务,在fReclamationTestSeconds后会调用livenessTimeoutTask。

事实上现非常easy只删除自身。

void RTSPServer::RTSPClientSession
::livenessTimeoutTask(RTSPClientSession* clientSession)
{
delete clientSession;
}

当server收到相应client的RR包时会调用noteLiveness,又一次计时。

fReclamationTestSeconds在RTPServer构造时传入,默觉得65s。表示如65s内未收到clientRTCP包即觉得client已断开。

假设在fReclamationTestSeconds的时间内再次调用noteLiveness,则该延迟任务会被设置成新的时间。原来的调度不再起作用。

  struct streamState
{ ServerMediaSubsession* subsession;
void* streamToken;
} * fStreamStates;

fStreamStates指向一个动态分配的数组。fNumStreamStates表示该数组包括的元素个数。

ServerMediaSession代表一个track(媒体流)。streamToken是void*类型的指针,但它指向StreamState类的对象。StreamState对象代表一个真正流动起来的数据流。这个流从XXXXFileSouce流向RTPSink。

能够看到一个ServerMediaSubSession相应一个StreamState。

但ServerMediaSubSession相应一个静态的流。能够被多个client重用。

如:多个client可能会请求同一个媒体文件里的track。StreamState代表一个动态的流。

ServerMediaSession

ServerMediaSession代表server端一个媒体文件。

其成员例如以下:

ServerMediaSubsession* fSubsessionsHead;

  ServerMediaSubsession* fSubsessionsTail;

  unsigned fSubsessionCounter;
char* fStreamName;
char* fInfoSDPString;
char* fDescriptionSDPString;
char* fMiscSDPLines;
struct timeval fCreationTime;
unsigned fReferenceCount;
Boolean fDeleteWhenUnreferenced;

能够看到其主要成员为fSubsessionsHead、fSubsessionsTail。代表该媒体文件里的多个媒体流track。

fStreamName为该媒体文件名称。

fDescritionSDPString代表SDP字符串。用于在client发送DESCRIBE命令时返回给client。

fReferenceCount为引用计数。

当将fDeleteWhenUnreferenced设置为true。且引用计数为0时。ServerMediaSession会被释放。

该值在构造函数中默认赋值为false。即全部ServerMediaSession即使不存在被client引用时,也不会被释放。对于长时间执行的server程序将会出现内存消耗耗尽的情况。

解决方式就是在构造时将fDeleteWhenUnreferenced的默认值赋值为true。

其它成员函数是用来操纵MediaSubSession。

MediaSubSession

假设一个媒体文件里既包括音频流又包括视频流。我们称这个媒体文件里包括两个track。每一个track相应一个ServerMediaSubsession。

ServerMediaSession* fParentSession;
netAddressBits fServerAddressForSDP;
portNumBits fPortNumForSDP;
private:
ServerMediaSubsession* fNext;
unsigned fTrackNumber; // within an enclosing ServerMediaSession
char const* fTrackId;

fParentSession指向该MediaSubSession所属的ServerMediaSession。

fNext指向下一个同属于一个ServerMediaSession的ServerMediaSubsession。假设只包括一个媒体流。则fNext指针为NULL。

fTrackNumber为track号。

在client发送DESCRIBE命令时,server端会为每一个媒体流分配一个TrackID。

fTrackId 为字符串指针,该字符串由”track”和fTrackNumber拼接而成。如track1、track2。

ServerMediaSubsession中只定义了空的接口,详细实现均放在其子类。

OnDemandServerMediaSubsession

HashTable* fDestinationsHashTable; 存储sessionID和Destinations的映射。

Destinations为目的地址。

每一个ClientSession在HashTable中都有与自己相应的项。

Destinations能够维护一对RTP和RTCP的port和地址信息。

StreamState

前面说过StreamState代表一个真正流动的流。如今让我们看下StreamState的到底实现了什么功能。

  OnDemandServerMediaSubsession& fMaster;
Boolean fAreCurrentlyPlaying;
unsigned fReferenceCount;
Port fServerRTPPort, fServerRTCPPort;
RTPSink* fRTPSink;
BasicUDPSink* fUDPSink;
float fStreamDuration;
unsigned fTotalBW;
RTCPInstance* fRTCPInstance;
FramedSource* fMediaSource;
float fStartNPT;
Groupsock* fRTPgs;
Groupsock* fRTCPgs;

fMaster为对OnDemandServerMediaSubsession或其子类的引用。

fReferenceCount为引用计数。

fServerRTPPort为RTPport

fServerRTCPPort为RTCPport

fRTPSink抽象Sink类。

fMediaSource为Souce基类。

能够看到StreamState既维护了Sink。又维护了Souce。事实上在StreamState

GroupSock主要用于处理组播。但也能够处理单播。

Groupsock* fRTPgs和   Groupsock* fRTCPgs为RTP和RTCP的地址。用于向RTP和RTCPport发送数据。

RTCPInstance

RTCPInsance是对RTCP通信的封装。

RTCP的功能是统计包的收发,为流量统计提供根据。因为其封装的比較完整。因此RTCPInstance与其它类间的关系不是那么紧密。

RTCPInstance靠RTPInterface提供支持。所以它既支持RTP over UDP,又支持RTP over TCP。

  void setByeHandler(TaskFunc* handlerTask, void* clientData,
Boolean handleActiveParticipantsOnly = True);
void setSRHandler(TaskFunc* handlerTask, void* clientData);
void setRRHandler(TaskFunc* handlerTask, void* clientData);
void setSpecificRRHandler(netAddressBits fromAddress, Port fromPort,
TaskFunc* handlerTask, void* clientData);

以上四个成员函数均是用来设置回调函数。在满足一定条件时该回调被调用。

setByeHandler用于设置在client结束与server的RTCP通信时的回调。

setSRHandler用于设置在收到client的SR包时的回调。在收到SR包时该回调被调用。

setRRHandler用于设置在收到client的RR包时的回调。在收到RR包时该回调被调用。

setSpecificRRHandler该成员函数与SetRRHandler的差别在于。它能够设置针对某一client的RR包的回调。

RTPClientSession就是调用此回调。为指定client注冊noteClientLiveness。用于检測client保活。如在一定时间内收不到RR包时即觉得client已经断开了连接。

此时将会删除相应的clientSession对象。这里提供了一种监视client执行状态的好方法。

每一个MediaSubSession相应一个StreamState对象。它们被保存在在ServerMediaClient中被StreamState数组中。

在收到client的PLAYM命令后。ServerMediaClient的响应函数内会为每一个StreamState调用play:

// Now, start streaming:

  for (i = 0; i < fNumStreamStates; ++i)

 {
if (subsession == NULL /* means: aggregated operation */
|| subsession == fStreamStates[i].subsession)
{
unsigned short rtpSeqNum = 0;
unsigned rtpTimestamp = 0;
if (fStreamStates[i].subsession == NULL) continue;
fStreamStates[i].subsession->startStream(fOurSessionId,
fStreamStates[i].streamToken,
(TaskFunc*)noteClientLiveness, this,
rtpSeqNum, rtpTimestamp, //略去部分代码 }
}

RTSPClientSession的handleCmd_SETUP中会依据ServerMediaSubSession的个数创建streamStates数组。

if (fStreamStates == NULL)
{
// 计算ServerMediaSubSession个数
ServerMediaSubsessionIterator iter(*fOurServerMediaSession);
for (fNumStreamStates = 0; iter.next() != NULL; ++fNumStreamStates) {}
fStreamStates = new struct streamState[fNumStreamStates];
iter.reset();
ServerMediaSubsession* subsession;
//将ServerMediaSubSession与streamStates通过fStreamStates数组进行关联
for (unsigned i = 0; i < fNumStreamStates; ++i)
{
subsession = iter.next();
fStreamStates[i].subsession = subsession;
fStreamStates[i].streamToken = NULL;
}
}

上述代码中与ServerMediaSubSession 关联的streamToken被赋值为NULL。

并会在后面的getStreamParameters中被赋值,最后一个參数为指针的引用。用于在getStreamParameters中改动该指针。

subsession->getStreamParameters(fOurSessionId, ourClientConnection->fClientAddr.sin_addr.s_addr,
clientRTPPort, clientRTCPPort,
tcpSocketNum, rtpChannelId, rtcpChannelId,
destinationAddress, destinationTTL, fIsMulticast,
serverRTPPort, serverRTCPPort,
fStreamStates[streamNum].streamToken);

getStreamParameters在OnDemandServerMediaSubsession又一次定义,能够看到创建StreamStates对象的代码:

// Set up the state of the stream.  The stream will get started later:
streamToken = fLastStreamToken
= new StreamState(*this, serverRTPPort, serverRTCPPort, rtpSink, udpSink,
streamBitrate, mediaSource,
rtpGroupsock, rtcpGroupsock);

能够看到StreamStates关联了Sink和Souce。

之所以要在OnDemandServerMediaSubsession又一次定义的getStreamParameters中分配StreamStates对象,是由于它定义了新的创建详细MediaSouce和MediaSink的虚函数。

virtual FramedSource* createNewStreamSource(unsigned clientSessionId,
unsigned& estBitrate) = 0;
// "estBitrate" is the stream's estimated bitrate, in kbps
virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock,
unsigned char rtpPayloadTypeIfDynamic,
FramedSource* inputSource) = 0;
 

StreamStates关联的MediaSouce和MediaSink均是详细的子类。

若媒体文件为H264码流,则相应的Souce为H264VideoStreamFramer。相应的Sink为H264VideoRTPSink。

在RTSPClientSession的handleCmd_PLAY中为每一个MediaSubSession循环调用startStream。并传入与MediaSubSession关联的StramStates对象指针:

for (i = 0; i < fNumStreamStates; ++i)
{
if (subsession == NULL /* means: aggregated operation */
|| subsession == fStreamStates[i].subsession)
{
unsigned short rtpSeqNum = 0;
unsigned rtpTimestamp = 0;
if (fStreamStates[i].subsession == NULL) continue;
fStreamStates[i].subsession->startStream(fOurSessionId,
fStreamStates[i].streamToken,
(TaskFunc*)noteClientLiveness, this,
rtpSeqNum, rtpTimestamp,
RTSPServer::RTSPClientConnection::handleAlternativeRequestByte, ourClientConnection);
} }

startStram内部调用了StreamStates的startPlaying:

void OnDemandServerMediaSubsession::startStream(unsigned clientSessionId,
void* streamToken,
TaskFunc* rtcpRRHandler,
void* rtcpRRHandlerClientData,
unsigned short& rtpSeqNum,
unsigned& rtpTimestamp,
ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,
void* serverRequestAlternativeByteHandlerClientData) {
StreamState* streamState = (StreamState*)streamToken;
Destinations* destinations
= (Destinations*)(fDestinationsHashTable->Lookup((char const*)clientSessionId));
if (streamState != NULL)
{
streamState->startPlaying(destinations,
rtcpRRHandler, rtcpRRHandlerClientData,
serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData);
RTPSink* rtpSink = streamState->rtpSink(); // alias
if (rtpSink != NULL)
{
rtpSeqNum = rtpSink->currentSeqNo(); rtpTimestamp = rtpSink->presetNextTimestamp();
}
}
}

streamStates的startPlaying内部则创建了RTCPInstance对象并调用了RTPSink的startPlaying函数:

fRTPSink->startPlaying(*fMediaSource, afterPlayingStreamState, this);

第一个參数即为详细的MediaSouce子类。

StartPlaying之后,Sink会调用Souce的getNextFrame获得一帧数据。

上面介绍的各种类是支撑LIVE555的各种基础设施。对于各种码流都是通用的。

2014.8.28于浙江杭州

LIVE555研究之五:RTPServer(二)的更多相关文章

  1. Live555研究之中的一个 源码编译

                                                Live555研究之中的一个 源代码编译 Live555 是一个为流媒体提供解决方式的跨平台的C++开源项目,它 ...

  2. live555学习经验链接二

    live555学习经验链接二:http://blog.csdn.net/nkmnkm/article/category/1066093/2

  3. Live555研究之二Sleep实现

    Live555通过一个while循环来不断读取socket,判断是否有连接进来,但是Live555并没有使用Sleep函数来让线程休眠多少毫秒来降低CPU占用率.Live555是通过select函数来 ...

  4. LIVE555研究之三:LIVE555基础

    LIVE555基础 LIVE555是为流媒体提供解决方式的跨平台C++开源项目.从今天起我们将正式開始深入LIVE555代码. 一.各库简要介绍 LIVE555下包括LiveMedia.UsageEn ...

  5. 经典算法研究系列:二、Dijkstra 算法初探

    July   二零一一年一月 本文主要参考:算法导论 第二版.维基百科. 一.Dijkstra 算法的介绍 Dijkstra 算法,又叫迪科斯彻算法(Dijkstra),算法解决的是有向图中单个源点到 ...

  6. Live555研究之一 源代码编译

    Live555 是一个为流媒体提供解决方案的跨平台的C++开源项目,它实现了对标准流媒体传输协议如RTP/RTCP.RTSP.SIP等的支持.Live555实现了对多种音视频编码格式的音视频数据的流化 ...

  7. Live555研究之三 RTSP Server处理请求

    RTSP Server会不断用select查询是否有socket连接,如果有则在(*handler->handlerProc)(handler->clientData, resultCon ...

  8. VS2010/MFC编程入门之五十二(Ribbon界面开发:创建Ribbon样式的应用程序框架)

    上一节中鸡啄米讲了GDI对象之画刷CBrush,至此图形图像的入门知识就讲完了.从本节开始鸡啄米将为大家带来Ribbon界面开发的有关内容.本文先来说说如何创建Ribbon样式的应用程序框架. Rib ...

  9. BZOJ 4241: 历史研究——莫队 二叉堆

    传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4241 题意:N个int范围内的数,M次询问一个区间最大的(数字*出现次数)(加权众数),可以 ...

随机推荐

  1. getColor()方法过时的替代方法

    Android SDK 升級到 23 之後,getResource.getColor(R.color.color_name) 過時 使用新加入的方法 ContextCompat.getColor(co ...

  2. 0x28 IDA*

    一个早上做完了我真牛B 就是A*用于DFS啊,现在我才发现迭代加深真是个好东西. poj3460 %了%了我们的目标是把它的顺序变对,那么第i个位置的值+1是要等于第i+1个位置的值的.对于一个操作, ...

  3. 0x14 hash

    被虐爆了 cry 我的hash是真的菜啊... poj3349 肝了一个上午心态崩了...一上午fail了42次我的天,一开始搞了个排序复杂度多了个log,而且是那种可能不同值相等的hash,把12种 ...

  4. php面向对象之get和set方法

    php面向对象之get和set方法 简介 1.自己写get或者set 2.用系统的魔术方法__get和__set 代码 <?php class Person{ private $userName ...

  5. 百度开源其NLP主题模型工具包,文本分类等场景可直接使用L——LDA进行主题选择本质就是降维,然后用于推荐或者分类

    2017年7月4日,百度开源了一款主题模型项目,名曰:Familia. InfoQ记者第一时间联系到百度Familia项目负责人姜迪并对他进行采访,在本文中,他将为我们解析Familia项目的技术细节 ...

  6. Hessian Servlet实例

    Servlet实例 业务场景 在下面的例子中我会发布一个简单的输出字符串的方法,然后在客户端调用并输出结果. 服务器端 环境搭建 在服务端,我们需要引入hessian和servlet的包.编写服务.配 ...

  7. [实例]ROS使用OpenCV读取图像并发布图像消息在rviz中显示

    思路: (1)使用opencv读取本地图像 (2)调用cv_bridge::CvImage().toImageMsg()将本地图像发送给rviz显示 一.使用opencv读取本地图像并发布图像消息 ( ...

  8. Python学习网络爬虫--转

    原文地址:https://github.com/lining0806/PythonSpiderNotes Python学习网络爬虫主要分3个大的版块:抓取,分析,存储 另外,比较常用的爬虫框架Scra ...

  9. 未在本地计算机上注册"Microsoft.Jet.OLEDB.4.0"提供程序的解决方法

    以下代码,打断点出现报错:未在本地计算机上注册“Microsoft.Jet.OLEDB.4.0”提供程序 DataSet ds=new DataSet(); try { string strCon = ...

  10. Mediator 基于内存的发布订阅

    Github Mediator 使用方法 /// <summary> /// 返回值 BaseEntity /// </summary> public class Ping1 ...