live555源码分析系列
live555源码分析(一)live555初体验
live555源码分析(二)基本组件上
live555源码分析(三)基本组件下
live555源码分析(四)RTSPServer分析
live555源码分析(五)DESCRIBE请求的处理
live555源码分析(六)SETUP和PLAY请求的处理
live555源码分析(七)播放过程
live555源码分析(八)多播
Groupsock
实现了UDP的单播和多播功能
使用Groupsock
单播
/* 创建Groupsock */
const unsigned char ttl = 255;
Groupsock groupsock(*env, "0.0.0.0", 0, ttl);
/* 清除所有的目的 */
groupsock.removeAllDestinations();
/* 添加目的地址 */
groupsock.addDestination(struct in_addr const& addr, Port const& port, unsigned sessionId);
...
/* 向所有的目的发送数据 */
groupsock.output(UsageEnvironment& env, unsigned char* buffer, unsigned bufferSize);
使用Groupsock
多播
/* 获得多播地址 */
struct in_addr destinationAddress;
destinationAddress.s_addr = chooseRandomIPv4SSMAddress(*env);
/* 多播端口 */
const unsigned short portNum = 18888;
const Port port(portNum);
const unsigned char ttl = 255;
/* 创建Groupsock */
Groupsock rtpGroupsock(*env, destinationAddress, port, ttl);
/* 向多播地址发送数据 */
groupsock.output(UsageEnvironment& env, unsigned char* buffer, unsigned bufferSize);
Groupsock
维护着一个目的链表,当使用单播时,链表存放所有目的地址,当使用多播时,链表只有一项,存放目的多播地址
Groupsock
的继承关系如下
Socket
的构造函数会创建一个UDP套接字
Groupsock
的构造函数会将指定的目的地址添加到目的链表中
Socket::Socket(UsageEnvironment& env, Port port) {
fSocketNum = setupDatagramSocket(fEnv, port);
}
Groupsock::Groupsock(UsageEnvironment& env, struct in_addr const& groupAddr,
Port port, u_int8_t ttl)
: OutputSocket(env, port),
fDests(new destRecord(groupAddr, port, ttl, 0, NULL)) //将目的地址添加到链表中
{
}
fDests
是Groupsock
维护的一个链表,如下
class Groupsock: public OutputSocket {
...
protected:
destRecord* fDests;
};
看一看destRecord
元素的定义
class destRecord {
...
public:
destRecord* fNext; //用于维护链表
GroupEId fGroupEId; //包含目的地址和目的端口
unsigned fSessionId; //会话ID
};
下面看一看在单播应用的情况下,Groupsock
如何添加目的地址
void Groupsock::addDestination(struct in_addr const& addr, Port const& port, unsigned sessionId {
/* 遍历链表 */
for (destRecord* dest = fDests; dest != NULL; dest = dest->fNext) {
if()
return;
}
/* 将目的地址插入链表中 */
fDests = createNewDestRecord(addr, port, 255, sessionId, fDests);
}
首先判断要添加的目的地址是否存在于链表中,如果存在则返回,不存在则将其插入链表中
下面再看一看Groupsock
的发送函数
Boolean Groupsock::output(UsageEnvironment& env, unsigned char* buffer, unsigned bufferSize,
DirectedNetInterface* interfaceNotToFwdBackTo) {
/* 遍历目的链表,对链表中的每个目的发送数据 */
for (destRecord* dests = fDests; dests != NULL; dests = dests->fNext) {
write(dests->fGroupEId.groupAddress().s_addr, dests->fGroupEId.portNum(),
dests->fGroupEId.ttl(),buffer, bufferSize);
}
}
RTPInterface
是RTP接口,通过包含Groupsock
实现了UDP单播和多播,另再实现了TCP单播
class RTPInterface {
Groupsock* gs() const { return fGS; }
/* 添加TCP目的 */
void addStreamSocket(int sockNum, unsigned char streamChannelId);
private:
Groupsock* fGS;
tcpStreamRecord* fTCPStreams; //维护TCP目的发送对象链表
};
在UDP情况下添加目的发送对象,会通过获取RTPInterface
中的Groupsock
来操作
在TCP情况下,通过addStreamSocket
来添加目的发送对象,RTPInterface
为TCP目的发送对象维护一个链表
看一下TCP目的对象的定义
class tcpStreamRecord {
...
public:
tcpStreamRecord* fNext; //用于维护链表
int fStreamSocketNum; //客户端套接字
unsigned char fStreamChannelId; //会话ID
};
下面再看一看如何addStreamSocket
的定义
void RTPInterface::addStreamSocket(int sockNum,
unsigned char streamChannelId) {
/* 添加到链表中 */
fTCPStreams = new tcpStreamRecord(sockNum, streamChannelId, fTCPStreams);
}
接下俩看RTPInterface
的发送函数
Boolean RTPInterface::sendPacket(unsigned char* packet, unsigned packetSize) {
/* 优先使用Groupsock发送 */
if (!fGS->output(envir(), packet, packetSize)) success = False;
/* 遍历TCP目的链表 */
tcpStreamRecord* nextStream;
for (tcpStreamRecord* stream = fTCPStreams; stream != NULL; stream = nextStream) {
sendRTPorRTCPPacketOverTCP(packet, packetSize,
stream->fStreamSocketNum, stream->fStreamChannelId)
}
}
首先调用Groupsock
发送,这是UDP的情况
然后会遍历TCP目的链表发送
这两种情况只有一种是有效的
RTCPInstance
是RTCP实例,内含RTPInterface
作为其传输接口,此外还定义了RTCP相关的一些内容
class RTCPInstance: public Medium {
public:
...
void setSRHandler(TaskFunc* handlerTask, void* clientData);
void setRRHandler(TaskFunc* handlerTask, void* clientData);
void addStreamSocket(int sockNum, unsigned char streamChannelId);
void incomingReportHandler1();
void processIncomingReport(...);
...
private:
...
RTPInterface fRTCPInterface;
...
};
RTSPServer
是RTSP服务器,负责处理客户端连接
RtspServer
的继承关系如下
RTSPServer
会创建一个TCP套接字,交给GenericMediaServer
,GenericMediaServer
会监听它
GenericMediaServer
定义了创建客户端连接和创建客户端会话的虚函数,RTSPServer
重新实现它们
当RTSPServer
接到一个客户端连接的时候,就会调用createNewClientConnection
创建一个客户连接
RTSPClientConnection
表示客户连接,定义了一系列的请求处理函数
class RTSPClientConnection: public GenericMediaServer::ClientConnection {
...
protected:
virtual void handleCmd_OPTIONS();
virtual void handleCmd_DESCRIBE(char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr);
...
};
RTSPClientSession
表示客户端连接会话,在客户端发起SETUP请求时,就会建立一个RTSPClientSession
,RTSPClientSession
定义了一系列的请求处理函数
class RTSPClientSession: public GenericMediaServer::ClientSession {
protected:
virtual void handleCmd_SETUP(RTSPClientConnection* ourClientConnection,
char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr);
virtual void handleCmd_PLAY(RTSPClientConnection* ourClientConnection,
ServerMediaSubsession* subsession, char const* fullRequestStr);
...
};
ServerMediaSession
表示服务器会话,在rtsp://ip:port/session
里的session字段就是表示请求的一个会话
ServerMediaSession
中维护一个ServerMediaSubsession
链表管理子会话
ServerMediaSession
的定义如下
class ServerMediaSession: public Medium {
public:
...
/* 获取sdp信息 */
char* generateSDPDescription();
/* 添加子会话 */
Boolean addSubsession(ServerMediaSubsession* subsession);
...
private:
/* 维护一个ServerMediaSubsession链表 */
ServerMediaSubsession* fSubsessionsHead;
ServerMediaSubsession* fSubsessionsTail;
};
其中的generateSDPDescription
函数是用于获取sdp信息,它将调用它每个子会话获取sdp媒体级信息
char* ServerMediaSession::generateSDPDescription() {
char const* const sdpPrefixFmt =
"v=0\r\n"
"o=- %ld%06ld %d IN IP4 %s\r\n"
"s=%s\r\n"
"i=%s\r\n"
...
/* 遍历所有子会话获取sdp信息 */
char* mediaSDP = sdp;
for (subsession = fSubsessionsHead; subsession != NULL;
subsession = subsession->fNext) {
...
char const* sdpLines = subsession->sdpLines();
...
}
}
addSubsession
将子会话添加到链表中
ServerMediaSession::addSubsession(ServerMediaSubsession* subsession) {
fSubsessionsTail->fNext = subsession;
fSubsessionsTail = subsession;
}
ServerMediaSubsession
表示子会话,其内含RTPSink
,表示一路音频或者视频流
ServerMediaSubsession
其实是一个虚基类,定义了一系列的虚函数,由派生类实现
ServerMediaSubsession
的定义如下
class ServerMediaSubsession: public Medium {
/* 获取媒体级的sdp描述 */
virtual char const* sdpLines() = 0;
virtual void getStreamParameters(...)=0; //SETUP阶段会调用
virtual void startStream(...)=0; //PLAY阶段会调用
};
派生于ServerMediaSubsession
的类由非常多
其中两个比较基本的是OnDemandServerMediaSubsession
和PassiveServerMediaSubsession
OnDemandServerMediaSubsession
实现了一个单播的子会话
PassiveServerMediaSubsession
实现了一个多播的子会话
然后又有许多类派生于OnDemandServerMediaSubsession
,如MPEG4VideoFileServerMediaSubsession
、H264VideoFileServerMediaSubsession
等
FramedSource
是数据源,用于产生音视频数据
FramedSource
是一个虚基类,定义了一个虚函数用于获取一帧数据,由派生类实现
class FramedSource: public MediaSource {
public:
...
virtual void doGetNextFrame() = 0;
...
};
派生于FramedSource
的类由许多ByteStreamFileSource
、H264VideoStreamFramer
等等
FramedSource
的派生类广泛应用了装饰者模式
举一个例子
ByteStreamFileSource* fileSource
= ByteStreamFileSource::createNew(*env, inputFileName);
H264VideoStreamFramer* videoSource
= H264VideoStreamFramer::createNew(*env, videoES);
其中ByteStreamFileSource
的作用是以字节流的形式读取文件,将fileSource
传递给H264VideoStreamFramer
之后,videoSource
的作用是解析H.264文件,获取一帧一帧的NALU
MediaSink
是数据的消费者,它会从source获取音视频数据,然后进行处理,发送给客户端
MediaSink
是一个虚基类,它定义了一系列的虚函数,由派生类实现
如何RTPSink
是MediaSink
的派生类,它定义了一些跟RTP相关的信息
MultiFramedRTPSink
继承RTPSink
,它处理了RTP的基本打包格式
H264VideoRTPSink
与MPEG4ESVideoRTPSink
等继承MultiFramedRTPSink
,实现了某种特定格式的音视频数据的RTP打包
MultiFramedRTPSink
实现了RTP的头部打包,并且提供一个虚函数给派生类实现自己特定的RTP打包
class MultiFramedRTPSink: public RTPSink {
public:
virtual void doSpecialFrameHandling(...); //派生类重新实现
private:
void buildAndSendPacket(Boolean isFirstPacket); //此函数实现了RTP打包
}
void MultiFramedRTPSink::buildAndSendPacket(Boolean isFirstPacket) {
unsigned rtpHdr = 0x80000000;
rtpHdr |= (fRTPPayloadType<<16);
rtpHdr |= fSeqNo;
fOutBuf->enqueueWord(rtpHdr);
...
}
关于live555的主要类就介绍到这里,后面的文章再来讨论细节