天天看點

live555之DESCRIBE

前言

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。我也懶得再看。這裡就不詳細介紹。我們大概了解整個函數的流程了。

其實這裡都是在看如何讀取檔案,但是最關鍵的,這些類的結構,和讀到檔案後,如何處理的,都沒詳細介紹,這裡等我以後有空再整理一下吧,不過整個流程大概隻有這些了。

live555之DESCRIBE

這是主要的資料結構,在server建立RTSPClineConnect,然後RTSPClineConnect開始向程序排程器注冊自己的特定socket端口和處理函數,可以處理來自特定socket的請求。在處理請求時候,一些檔案的讀寫都是依靠serverMediaSession來實作的。而serverMediaSession,卻僅僅是一個代理,每一種檔案類型,都事通過自己的subsession來實作的,這裡進行了一些封裝。具體ServerMediaSession如何實作,以及內建關系,這裡暫時不在詳細介紹。

後記

廢了一天功夫,終于整理出這篇部落格,這裡基本上想通過這篇部落格看懂大概live555的願望估計大家很難實作了,不過這些可以給那些想讀,可是苦于毫無頭緒的人一些啟發。

繼續閱讀