前言
DESCRIBE
.請求直接傳回一些伺服器可用參數,這些其實也就是RTSP支援的一些通訊協定,
正文
第一次
OPTION
指令基本沒做啥事情,也就是建立一個
mediaSession
,我們上一篇也介紹了一下,這裡不再全部介紹。直接開始
DESCRIBE
請求相應流程。
void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) {
Boolean parseSucceeded = parseRTSPRequestString((char*)fRequestBuffer, fLastCRLF + - fRequestBuffer,
cmdName, sizeof cmdName,
urlPreSuffix, sizeof urlPreSuffix,
urlSuffix, sizeof urlSuffix,
cseq, sizeof cseq,
sessionIdStr, sizeof sessionIdStr,
contentLength);
......
handleCmd_DESCRIBE(urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
......
send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), );
}
這裡總共隻有三個操作,解析請求,處理請求,和傳回相應,解析處理流程比較無聊,這裡也不在詳細閱讀, 請求指令隻這些,解析後如下:
DESCRIBE rtsp://192.168.1.100:8554/test.mkv RTSP/1.0
CSeq:
User-Agent: LibVLC/ (LIVE555 Streaming Media v2016)
Accept: application/sdp
//解析後:
cmdName "DESCRIBE", urlPreSuffix "", urlSuffix "test.mkv", CSeq "3", Content-Length ,
處理的話,我們直接追蹤下,因為這裡有讀取檔案,等等比較關鍵的操作。
void RTSPServer::RTSPClientConnection
::handleCmd_DESCRIBE(char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) {
......
session = fOurServer.lookupServerMediaSession(urlTotalSuffix);
......
sdpDescription = session->generateSDPDescription();
......
}
session是通過
DynamicRTSPServer
的
lookupServerMediaSession
函數建立的,這裡有興趣的童鞋可以看下,不太複雜。
handleCmd_DESCRIBE
->
generateSDPDescription
,這是讀取檔案的參數的函數
char* ServerMediaSession::generateSDPDescription() {
......
char const* sdpLines = subsession->sdpLines();
......
}
這是開始讀取檔案,得到必要編碼規則,然後生成視訊資訊發給用戶端。可是這裡比較複雜,暫時停下來,吧整個發送的代碼部分整理清楚,這裡得到視訊的一些資訊,最後和必要的通訊頭結合形成一個完整的通訊資訊,形式如下
RTSP/ OK
CSeq:
Date: Sun, Sep :: GMT
Content-Base: rtsp://192.168.1.100:8554/test.mkv/
Content-Type: application/sdp
Content-Length:
v=
o=- IN IP4
s=Matroska video+audio+(optional)subtitles, streamed by the LIVE555 Media Server
i=test.mkv
t=
a=tool:LIVE555 Streaming Media v2017
a=type:broadcast
a=control:*
a=range:npt=-
a=x-qt-text-nam:Matroska video+audio+(optional)subtitles, streamed by the LIVE555 Media Server
a=x-qt-text-inf:test.mkv
m=video RTP/AVP
c=IN IP4
b=AS:
a=rtpmap: H264/
a=fmtp: packetization-mode=;profile-level-id=;sprop-parameter-sets=Z2QAKazZAFAFuwEQAGXTsBMS0Ajxgxlg,aOrssiw=
a=control:track1
m=audio RTP/AVP
c=IN IP4
b=AS:
a=rtpmap: AC3/
a=control:track2
這些資訊到底如何讀取,我這裡就不一一介紹,如果誰想完全搞懂,可以慢慢順着這個思路一一整理,最關鍵的視訊資訊的讀取,我們這裡開始完整介紹
之前我們讀到
subsession->sdpLines();
可以讀取檔案的部分資訊,我們這裡慢慢開始讀這部分代碼。對于h264檔案,subsession,類型是
H264VideoFileServerMediaSubsession
。
OnDemandServerMediaSubsession::sdpLines() {
FramedSource* inputSource = createNewStreamSource(, estBitrate);
Groupsock* dummyGroupsock = createGroupsock(dummyAddr, );
RTPSink* dummyRTPSink = createNewRTPSink(dummyGroupsock, rtpPayloadType, inputSource);
return fSDPLines;
}
這裡建立了一個
FramedSource
。,類型是
H264VideoStreamFramer
,然後通過
createNewRTPSink
調用開始讀取,
dummyRTPSink
類型為
H264VideoRTPSink
void OnDemandServerMediaSubsession
::setSDPLinesFromRTPSink(RTPSink* rtpSink, FramedSource* inputSource, unsigned estBitrate) {
//一大堆用來擷取吧RTPSink設定成我們需要的參數的方法,
char const* auxSDPLine = getAuxSDPLine(rtpSink, inputSource);
//生成我們需要的參數
}
注意這裡通過vs,直接可以進入下面方法,可是這個方法調用是不對的,調用的是,子類
H264VideoFileServerMediaSubsession
對此方法的重寫。
//沒有調用這個方法
char const* OnDemandServerMediaSubsession
::getAuxSDPLine(RTPSink* rtpSink, FramedSource* /*inputSource*/) {
// Default implementation:
return rtpSink == NULL ? NULL : rtpSink->auxSDPLine();
}
//真正的是這個方法
char const* H264VideoFileServerMediaSubsession::getAuxSDPLine(RTPSink* rtpSink, FramedSource* inputSource) {
fDummyRTPSink->startPlaying(*inputSource, afterPlayingDummy, this);
checkForAuxSDPLine(this);
}
envir().taskScheduler().doEventLoop(&fDoneFlag);
return fAuxSDPLine;
}
看到這,廢了這麼大的勁,麻蛋還是雲裡霧裡,并且霧氣越來越多,那我們還是堅持,繼續看,總有柳暗花明一天,不過看到注釋,我想大家應該就可以放心一點了,我們繼續
Boolean MediaSink::startPlaying(MediaSource& source,
afterPlayingFunc* afterFunc,
void* afterClientData) {
......
fSource = (FramedSource*)&source;
fAfterFunc = afterFunc;
fAfterClientData = afterClientData;
return continuePlaying();
}
Boolean H264or5VideoRTPSink::continuePlaying() {
......
return MultiFramedRTPSink::continuePlaying();
}
Boolean MultiFramedRTPSink::continuePlaying() {
// Send the first packet.
// (This will also schedule any future sends.)
buildAndSendPacket(True);
return True;
}
Boolean MultiFramedRTPSink::continuePlaying() {
// Send the first packet.
// (This will also schedule any future sends.)
buildAndSendPacket(True);
return True;
}
void MultiFramedRTPSink::buildAndSendPacket(Boolean isFirstPacket) {
packFrame();
}
void MultiFramedRTPSink::packFrame() {
fSource->getNextFrame(fOutBuf->curPtr(), fOutBuf->totalBytesAvailable(),
afterGettingFrame, this, ourHandleClosure, this);
}
void FramedSource::getNextFrame(unsigned char* to, unsigned maxSize,
afterGettingFunc* afterGettingFunc,
void* afterGettingClientData,
onCloseFunc* onCloseFunc,
void* onCloseClientData) {
doGetNextFrame();
}
void MPEGVideoStreamFramer::doGetNextFrame() {
......
continueReadProcessing();
}
void MPEGVideoStreamFramer::continueReadProcessing() {
unsigned acquiredFrameSize = fParser->parse();
......
//這些烏七八糟的是開始解析檔案了。
}
其實到這裡,也就開始讀檔案了,讀完以後開始解析,可是也許有人會說,parse()是讀取了嗎?我打開函數,覺得完全不像啊,這種變态的設計,我也是有些無語,我們還是慢慢一一讀下代碼吧。
unsigned H264or5VideoStreamParser::parse() {
......
while ((first4Bytes = test4Bytes()) != ) {
get1Byte();
setParseState(); // ensures that we progress over bad data
}
......
u_int8_t get1Byte() { // byte-aligned
ensureValidBytes();
}
void StreamParser::ensureValidBytes1(unsigned numBytesNeeded) {
......
fInputSource->getNextFrame(&curBank()[fTotNumValidBytes],
maxNumBytesToRead,
afterGettingBytes, this,
onInputClosure, this);
throw NO_MORE_BUFFERED_INPUT;
}
void FramedSource::getNextFrame(unsigned char* to, unsigned maxSize,
afterGettingFunc* afterGettingFunc,
void* afterGettingClientData,
onCloseFunc* onCloseFunc,
void* onCloseClientData) {
doGetNextFrame();
}
void ByteStreamFileSource::doGetNextFrame() {
doReadFromFile();
}
void ByteStreamFileSource::doReadFromFile() {
//讀檔案,
fFrameSize = fread(fTo, , fMaxSize, fFid);
//通過定時,的重新調用我們的FramedSource::afterGetting這個函數,目的是為了讓伺服器長時間不響應其他服務吧,具體不了解。
nextTask() = envir().taskScheduler().scheduleDelayedTask(,
(TaskFunc*)FramedSource::afterGetting, this);
}
通過
FramedSource::afterGetting
,這是定時任務,被計時器觸發,然後重新調用
parse()
開始解析資料,至于說如何解析h264。我也懶得再看。這裡就不詳細介紹。我們大概了解整個函數的流程了。
其實這裡都是在看如何讀取檔案,但是最關鍵的,這些類的結構,和讀到檔案後,如何處理的,都沒詳細介紹,這裡等我以後有空再整理一下吧,不過整個流程大概隻有這些了。

這是主要的資料結構,在server建立RTSPClineConnect,然後RTSPClineConnect開始向程序排程器注冊自己的特定socket端口和處理函數,可以處理來自特定socket的請求。在處理請求時候,一些檔案的讀寫都是依靠serverMediaSession來實作的。而serverMediaSession,卻僅僅是一個代理,每一種檔案類型,都事通過自己的subsession來實作的,這裡進行了一些封裝。具體ServerMediaSession如何實作,以及內建關系,這裡暫時不在詳細介紹。
後記
廢了一天功夫,終于整理出這篇部落格,這裡基本上想通過這篇部落格看懂大概live555的願望估計大家很難實作了,不過這些可以給那些想讀,可是苦于毫無頭緒的人一些啟發。