继承关系:
H264BufferedPacket->BufferedPacket
BufferedPacket用于存储媒体数据的RTP包内容,它的子类具体到媒体类型,如H264BufferedPacket类。值得注意的有两点:
1)构造函数BufferedPacket()中申请了MAX_PACKET_SIZE(10000)大小的unsigned char数组。
2)一个重要函数是
Boolean fillInData (RTPInterface &rtpInterface, Boolean &packetReadWasIncomplete)
{
rtpInterface.handleRead(&fBuf);
}
实现了将rtpInterface指向的数据存入fBuf中的功能。
ReorderingPacketBuffer类
该类用于存放多个BufferedPacket对象(可能是对象指针链表,有待考察),作为Source类中组织多个BufferedPacket对象的场所。
H264BufferedPacketFactory->BufferedPacketFactory
H264VideoRTPSink继承关系:H264VideoRTPSink->VideoRTPSink->MultiFramedRTPSink->RTPSink->MediaSink。
H264FUAFragmenter继承关系:H264FUAFragmenter->FramedFilter->FramedSource->MediaSource->Medium。
解析数据的类 H264VideoStreamParser->MPEGVideoStreamParser->StreamParser。
Source和Sink:可以把source理解为发送端的流,sink理解为接受端。MediaSink是各种类型的Sink的基类,MediaSource是各种类型Source的基类,各种类型的流媒体格式和编码的支持即是通过对这两个类的派生实现的。Source和Sink通过RTP子会话(MediaSubSession)联系在一起。
发送H264的流程图如下:
发码流的过程:
RTSPClientSession::incomingRequestHandler->RTSPClientSession::incomingRequestHandler1->RTSPClientSession::handleRequestBytes->RTSPClientSession::handleCmd_withinSession->RTSPClientSession ::handleCmd_PLAY->OnDemandServerMediaSubsession::startStream-
>RTCPInstance::createNew->MediaSink::startPlaying->H264VideoRTPSink::continuePlaying->MultiFramedRTPSink::continuePlaying-> MultiFramedRTPSink::buildAndSendPacket->MultiFramedRTPSink::packFrame->FramedSource::getNextFrame->H264FramedLiveSource->FramedSource::afterGetting->MultiFramedRTPSink::afterGettingFrame
->MultiFramedRTPSink::afterGettingFrame1->MultiFramedRTPSink::sendPacketIfNecessary->MultiFramedRTPSink::sendNext
->MultiFramedRTPSink::buildAndSendPacket......
2013.10.24
今天发现服务端在响应RTSP的时候执行了两次FramedSource::createNewStreamSource,调用顺序分别如下:
RTSPServer::RTSPClientSession::handleCmd_DESCRIBE->
ServerMediaSession::generateSDPDescription->
OnDemandServerMediaSubsession::sdpLines->
FramedSource::createNewStreamSource.
RTSPServer::RTSPClientSession::handleCmd_SETUP->
OnDemandServerMediaSubsession::getStreamParameters{虚createNewStreamSource要重写->虚createNewRTPSink不要重写}->
.
处理handleCmd_DESCRIBE的时候,执行了createNewStreamSource,根据创建的Source设置SDP(setSDPLinesFromRTPSink),执行完后,删除Source(closeStreamSource);处理handleCmd_SETUP的时候,又执行了createNewStreamSource,此次没有马上删除,而是用来读出数据。
2013.10.29
在服务器端,如果是文件模式,需要在doGetNextFrame()里给出数据,并给fFrameSize赋予数据的长度,如果fFrameSize==0,即没读入数据;如果读完数据,调用handleClosure(this),以后调用的顺序为StreamParser::onInputClosure();-》FramedSource::handleClosure();-》MultiFramedRTPSink::ourHandleClosure();-》MediaSink::onSourceClosure();-》StreamState::reclaim(),到此释放相关资源。
2013.10.30
今天看了OnDemandServerMediaSubsession::getStreamParameters的代码,主要做的事情有:
createNewStreamSource->rtpGroupsock -> new Groupsock->createNewRTPSink->
streamToken = fLastStreamToken
= new StreamState(*this, serverRTPPort, serverRTCPPort, rtpSink, udpSink,streamBitrate, mediaSource, rtpGroupsock, rtcpGroupsock);
2013.10.31
客户端和服务端的交互有如下内容:
RTSP命令 live555中处理函数
---------------------------------------------
OPTIONS <---> handleCmd_OPTIONS
DESCRIBE <---> handleCmd_DESCRIBE
SETUP <---> handleCmd_SETUP
PLAY <---> handleCmd_PLAY
PAUSE <---> handleCmd_PAUSE
TEARDOWN <---> handleCmd_TEARDOWN
GET_PARAMETER <---> handleCmd_GET_PARAMETER
SET_PARAMETER <---> handleCmd_SET_PARAMETER
MediaSubsession::initiate
{
fRTPSocket = new Groupsock->
fRTCPSocket = new Groupsock->
fRTCPInstance = RTCPInstance::createNew
}
客户端TCP连接的流程:
RTSPClient::sendRequest->RTSPClient::openConnection->RTSPClient::connectToServer
服务端接收连接的流程: RTSPServer::incomingConnectionHandlerRTSP->RTSPServer::incomingConnectionHandlerRTSP1->RTSPServer::createNewClientSession->RTSPClientSession::RTSPClientSession
2014.1
RTSPClientSession::handleCmd_TEARDOWN
{
fSessionIsActive = False; // 就只这一句。
}
紧接着会执行如下:
if (!fSessionIsActive) {
//收到TEARDOWN后,此值变false。
if (fRecursionCount > 0) closeSockets(); else delete this;
// Note: The "fRecursionCount" test is for a pathological situation where we got called recursively while handling a command.
// In such a case we don't want to actually delete ourself until we leave the outermost call.
这样,断开了,SOCKET,也清除了资源。
}
客户端的暂停和拖动。
暂停
暂停 playerIn.rtspClient->pauseMediaSession(*(playerIn.Session)); 播放 playerIn.rtspClient->playMediaSession(*(playerIn.Session), -1); //will resume
拖动
float SessionLength = Session->playEndTime() //先得到播放时间区域,在SDP解析中。
先PAUSE***,再rtspClient->PlayMediaSession(Session, start); //start less than the "SessionLength "
服务端如何发送SPS,PPS(2014)。
服务端在响应Describe命令时,会读入数据,查找SPS,PPS,并用base64加密,发送给客户端,详见RTSPClientSession::handleCmd_DESCRIBE,
session->generateSDPDescription。读取数据分析:详见OnDemandServerMediaSubsession::setSDPLinesFromRTPSink,H264VideoFileServerMediaSubsession::getAuxSDPLine。
查找SPS,PPS的分析:在H264VideoStreamParser::parse函数中, switch (nal_unit_type)时,如果为7,8即为SPS,PPS,顺便保存数据,详见
H264VideoStreamFramer::saveCopyOfSPS,H264VideoStreamFramer::saveCopyOfPPS。
发送SPS,PPS的分析:在H264VideoFileServerMediaSubsession::checkForAuxSDPLine1中,会多次调用checkForAuxSDPLine,实际上是在检查
H264VideoStreamFramer是否得到SPS,PPS(详见:H264VideoRTPSink::auxSDPLine中的framerSource->getSPSandPPS(sps, spsSize, pps, ppsSize)),
紧接着会进行base64编码(详见base64Encode((char*)sps, spsSize)等)并联合其他信息保存在fSDPLines里,最终保存在fResponseBuffer,然后发送给客户端。详见RTSPClientSession::handleRequestBytes,send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0)。
live555鉴权(2014)
服务端设置鉴权的代码如下:
UserAuthenticationDatabase* authDB = NULL;
authDB = new UserAuthenticationDatabase;
authDB->addUserRecord("admin", "password"); // replace these with real strings
......
鉴权流程为:客户端在sendOptionsCmd时,需添加用户名与密码,如:char *strOption = m_pClient->sendOptionsCmd(sUrl,"admin","password");
鉴权的流程为:客户端发送DESCRIBE命令后,会收到服务器发来的鉴权要求及其随机数nonce,客户端发送用户及其反馈信息response(response则是client通过digest计算的响应),服务端
也根据已知的信息也计算response,并与客户端发来的response比较。
示意图如下:
[1]C-->S
OPTIONS rtsp://172.16.201.67:8554/record RTSP/1.0
CSeq: 2
User-Agent: LibVLC/2.1.3 (LIVE555 Streaming Media v2014.01.21)
[2]S-->C
RTSP/1.0 200 OK
CSeq: 2
Date: Sat, May 31 2014 14:16:42 GMT
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER
[3]C-->S
DESCRIBE rtsp://10.0.0.10:8554/h264ESVideoTest RTSP/1.0
CSeq: 3
User-Agent: LibVLC/2.1.3 (LIVE555 Streaming Media v2014.01.21)
Accept: application/sdp
[4]S-->C
RTSP/1.0 401 Unauthorized
CSeq: 3
Date: Sat, May 31 2014 14:16:43 GMT
WWW-Authenticate: Digest realm="LIVE555 Streaming Media", nonce="73724068291777415fec38a1593568e5"
[5]C-->S
DESCRIBE rtsp://10.0.0.10:8554/h264ESVideoTest RTSP/1.0
CSeq: 4
Authorization: Digest username="admin", realm="LIVE555 Streaming Media", nonce="73724068291777415fec38a1593568e5", uri="rtsp://172.16.201.67:8554/record",
response="b8c755d897abddd0206954bab0e0b763"
User-Agent: LibVLC/2.1.3 (LIVE555 Streaming Media v2014.01.21)
Accept: application/sdp
[6]S
lookupPassword(zjh) returned password 123
客户端指定端口(2014)
在subsession->initiate函数之前,调用subsession->setClientPortNum,如subsession->setClientPortNum(3000)即为:RTP端口为3000,RTCP端口为30001。
# 0x74d5c41f 处最可能的异常: Microsoft C++ 异常: 内存位置 0x0993eb74 处的 int的类似错误。
异常来自throw NO_MORE_BUFFERED_INPUT;因取不到数据。
点播不存在的文件,服务器会怎么处理。
在void RTSPServer::RTSPClientSession
::handleCmd_DESCRIBE里,会走入如下代码。
ServerMediaSession* session = fOurServer.lookupServerMediaSession(urlTotalSuffix);
if (session == NULL)
{
handleCmd_notFound(cseq);
break;
}
live555服务器如何检测客户端的播放状态。
服务器会把客户端发来的RR包当做心跳, 如何超过最大时间(RTSPServer构造函数的reclamationTestSeconds参数)未收到RR包,服务器
RTSPServer::RTSPClientSession会自杀,参见函数void RTSPServer::RTSPClientSession::livenessTimeoutTask(RTSPClientSession* clientSession)。
还需搞懂的问题
# 发送如何控制帧率,速度。
# 服务端如何发送SPS,PPS(已分析)。
# 如何同时发音视频数据。
# 如何发送其他格式的码流。
# PassiveServerMediaSubsession什么时候用。
# 网络断线的话,live555是怎么处理的。
# 怎么验证密码(已分析)。
#客户端如何指定RTP,RTCP的端口(已分析)。
# 0x74d5c41f 处最可能的异常: Microsoft C++ 异常: 内存位置 0x0993eb74 处的 int的类似错误(已分析)。
#点播不存在的文件,服务器会怎么处理(已分析)。
#live555服务器如何检测客户端的播放状态(已分析)。
#live555的客户端如何处理丢包的RTP数据。