天天看點

vlc源碼分析(三) 調用live555接收RTP資料

首先了解RTSP/RTP/RTCP相關概念,尤其是了解RTP協定:​​RTP與RTCP協定介紹(轉載)​​。

  vlc使用子產品加載機制調用live555,調用live555的檔案是live555.cpp。

一、幾個重要的類  

  以下向左箭頭(“<-”)為繼承關系。

1. RTPInterface

  RTPInterface是RTPSource的成員變量,其成員函數handleRead會讀取網絡資料存入BufferedPacket内,該類最終會調到UDP的發送接收函數。

Boolean RTPInterface::handleRead(unsigned char* buffer, unsigned bufferMaxSize,
                 unsigned& bytesRead, struct sockaddr_in& fromAddress, Boolean& packetReadWasIncomplete)      

2. BufferedPacket

  BufferedPacket:用于存儲媒體的RTP資料包

  BufferedPacket<-H264BufferedPacket:用于存儲H264媒體RTP資料包

  該類有一個重要函數fillInData,是由RTPInterface讀取資料存入包中。

Boolean BufferedPacket::fillInData(RTPInterface& rtpInterface, Boolean& packetReadWasIncomplete);      

  相對于BufferedPacket,有對應的工廠類:

  BufferedPacketFactory:工廠模式生成BufferedPacket包

  BufferedPacketFactory<-H264BufferedPacketFactory:專門生産H264BufferedPacket的工廠

  在SessionsSetup的時候(也是子產品加載的時候),會根據Source類型,標明生産BufferedPacket的工廠類型,即如果Source是H264格式的話,就會new H264BufferedPacketFactory,之後在接收資料的時候就會生産H264BufferedPacket用于存儲H264媒體資料。

  ReorderingPacketBuffer:MultiFramedRTPSource的成員變量,用于管理多個BufferedPacket。

3. Source相關類  

  Source相關類的繼承關系:Medium<-MediaSource<-FramedSource<-RTPSource<-MultiFramedRTPSource<-H264VideoRTPSource。

  在SessionsSetup的時候,會根據資料源的類型,標明Source的類型,即如果資料源是H264格式的話,就會調用

static H264VideoRTPSource* createNew(UsageEnvironment& env, Groupsock* RTPgs,
  unsigned char rtpPayloadFormat,
  unsigned rtpTimestampFrequency = 90000);      

二、播放流程的建立

  播放流程的建立可以參考​​vlc源碼分析之播放流程​​。

三、接收RTP資料

  vlc在播放IPC時,會開啟一個線程接收網絡資料,該線程接收網絡資料後會調用Demux()進行分離(因為可能是音頻,也可能是視訊)。Demux()首先将必要的接口,如StreamRead、StreamClose注冊下去,然後就進入事件循環:

p_sys->scheduler->doEventLoop( &p_sys->event_data );      

  如果有網絡資料到來了,Demux()會做兩件事,第一件事是分析RTP包,放入ReorderingPacketBuffer管理的BufferedPacket中,堆棧如下圖所示:

vlc源碼分析(三) 調用live555接收RTP資料

  第二件事是讀取的BufferedPacket,進行一系列拆包操作後,将資料放入資料fifo中,堆棧如下圖所示:

vlc源碼分析(三) 調用live555接收RTP資料

  doEventLoop會進入死循環,直到p_sys->event_data的值被中斷或者逾時改變,進而退出循環。當有網絡資料到來的時候,doEventLoop會執行SingleStep->...->doGetNextFrame1(),在doGetNextFrame1()函數中讀取RTP資料。這個過程的代碼及注釋如下:

// 做了兩件事,一件是分析RTP包,放入ReorderingPacketBuffer管理的BufferedPacket中;
// 另一件是讀取的BufferedPacket,進行一系列拆包操作後,将資料放入資料fifo中
void MultiFramedRTPSource::networkReadHandler1() {
  BufferedPacket* bPacket = fPacketReadInProgress;
  if (bPacket == NULL) {
    // Normal case: Get a free BufferedPacket descriptor to hold the new network packet:
    bPacket = fReorderingBuffer->getFreePacket(this);
  }

  // Read the network packet, and perform sanity checks on the RTP header:
  Boolean readSuccess = False;
  // do-while(0)結構,出現錯誤直接break
  do {
    Boolean packetReadWasIncomplete = fPacketReadInProgress != NULL;
    if (!bPacket->fillInData(fRTPInterface, packetReadWasIncomplete)) break;
    if (packetReadWasIncomplete) {
      // We need additional read(s) before we can process the incoming packet:
      fPacketReadInProgress = bPacket;
      return;
    } else {
      fPacketReadInProgress = NULL;
    }
#ifdef TEST_LOSS
    setPacketReorderingThresholdTime(0);
       // don't wait for 'lost' packets to arrive out-of-order later
    if ((our_random()%10) == 0) break; // simulate 10% packet loss
#endif

    // Check for the 12-byte RTP header:
    if (bPacket->dataSize() < 12) break;
    // 讀取RTP頭,向前移4個位元組
    unsigned rtpHdr = ntohl(*(u_int32_t*)(bPacket->data())); ADVANCE(4);
    // 讀取RTP頭中的标記位
    Boolean rtpMarkerBit = (rtpHdr&0x00800000) != 0;
    // 讀取時間戳,向前移4個位元組
    unsigned rtpTimestamp = ntohl(*(u_int32_t*)(bPacket->data()));ADVANCE(4);
    // 讀取SSRC,向前移4個位元組
    unsigned rtpSSRC = ntohl(*(u_int32_t*)(bPacket->data())); ADVANCE(4);

    // Check the RTP version number (it should be 2):
    // 檢查RTP頭版本,不是2的話,break
    if ((rtpHdr&0xC0000000) != 0x80000000) break;

    // Skip over any CSRC identifiers in the header:
    // 跳過CSRC計數位元組
    unsigned cc = (rtpHdr>>24)&0xF;
    if (bPacket->dataSize() < cc) break;
    ADVANCE(cc*4);

    // Check for (& ignore) any RTP header extension
    // 如果擴充頭标志被置位
    if (rtpHdr&0x10000000) {
      if (bPacket->dataSize() < 4) break;
      // 擷取擴充頭
      unsigned extHdr = ntohl(*(u_int32_t*)(bPacket->data())); ADVANCE(4);
      // 擷取擴充位元組數
      unsigned remExtSize = 4*(extHdr&0xFFFF);
      if (bPacket->dataSize() < remExtSize) break;
      // 直接跳過擴充位元組???
      ADVANCE(remExtSize);
    }

    // Discard any padding bytes:
    // 如果填充标志被置位,直接丢棄不處理
    if (rtpHdr&0x20000000) {
      if (bPacket->dataSize() == 0) break;
      unsigned numPaddingBytes
    = (unsigned)(bPacket->data())[bPacket->dataSize()-1];
      if (bPacket->dataSize() < numPaddingBytes) break;
      bPacket->removePadding(numPaddingBytes);
    }
    // Check the Payload Type.
    // 檢查載荷類型,如果源資料H264類型,則其值為96
    // 如果與我們生成的source類型不同,則break
    if ((unsigned char)((rtpHdr&0x007F0000)>>16)
    != rtpPayloadFormat()) {
      break;
    }

    // The rest of the packet is the usable data.  Record and save it:
    if (rtpSSRC != fLastReceivedSSRC) {
      // The SSRC of incoming packets has changed.  Unfortunately we don't yet handle streams that contain multiple SSRCs,
      // but we can handle a single-SSRC stream where the SSRC changes occasionally:
      fLastReceivedSSRC = rtpSSRC;
      fReorderingBuffer->resetHaveSeenFirstPacket();
    }
    // RTP包序号,随RTP資料包而自增,由接收者用來探測包損失
    unsigned short rtpSeqNo = (unsigned short)(rtpHdr&0xFFFF);
    Boolean usableInJitterCalculation
      = packetIsUsableInJitterCalculation((bPacket->data()),
                          bPacket->dataSize());
    struct timeval presentationTime; // computed by:
    Boolean hasBeenSyncedUsingRTCP; // computed by:
    // 根據資料包的一些資訊,進行一些計算和記錄
    receptionStatsDB()
      .noteIncomingPacket(rtpSSRC, rtpSeqNo, rtpTimestamp,
              timestampFrequency(),
              usableInJitterCalculation, presentationTime,
              hasBeenSyncedUsingRTCP, bPacket->dataSize());

    // Fill in the rest of the packet descriptor, and store it:
    struct timeval timeNow;
    gettimeofday(&timeNow, NULL);
    // 将計算所得的一些參數再指派到包中
    bPacket->assignMiscParams(rtpSeqNo, rtpTimestamp, presentationTime,
                  hasBeenSyncedUsingRTCP, rtpMarkerBit,
                  timeNow);
    // 經過以上判斷和檢查,沒有發現問題,則由管理類fReorderingBuffer存儲包
    if (!fReorderingBuffer->storePacket(bPacket)) break;

    readSuccess = True;// 讀取成功
  } while (0);
  if (!readSuccess) fReorderingBuffer->freePacket(bPacket);// 如果讀取不成功,則釋放記憶體

  // 将讀取到的資料包送至資料fifo中,等待解碼線程解碼
  doGetNextFrame1();
  // If we didn't get proper data this time, we'll get another chance
}      

四、組裝RTP包為音視訊資料包

4.1 組幀要點

1. 預設一個音頻包就是一個音頻幀;

2. sps,pps會和I幀組成一個完整的幀;

3. 幀頭:對于sps,預設為幀頭。對于判斷是slice開頭的包,根據片中第一個宏塊的位址(first_mb_in_slice,讀取時注意是哥倫布編碼的)判斷目前片是否是視訊幀的首片;注意,h264組幀協定RFC3984裡,FU-header中的S和E辨別的是片頭和片尾,不是幀。

4. 幀尾:對于視訊資料,如果marker為1,則該片對應的幀為幀尾。

5. 同一視訊幀有相同的timestamp;

4.2 組幀執行個體

 以h264組幀幀為例,組幀協定是RFC3984。上層讀取到一個個的RTP包之後,需要讀取RTP包的payload組裝成h264的視訊幀。那麼按照什麼規則組裝呢?這裡有一個rfc3984協定,描述了RTP封裝h264的方法。簡單描述如下:

1. 單個NAL單元包:荷載中隻包含一個NAL單元。NAL頭類型域等于原始 NAL單元類型,即在範圍1到23之間,此時的RTP包僅僅是将NALU的資料前加12個位元組的RTP頭,即RTP header(12 bytes)+NALU;

2. 聚合包:本類型用于聚合多個NAL單元到單個RTP荷載中。本包有四種版本,單時間聚合包類型A (STAP-A),單時間聚合包類型B (STAP-B),多時間聚合包類型(MTAP)16位位移(MTAP16), 多時間聚合包類型(MTAP)24位位移(MTAP24)。賦予STAP-A, STAP-B, MTAP16, MTAP24的NAL單元類型号分别是 24,25, 26, 27;

3. 分片單元:用于分片單個NAL單元到多個RTP包。現存兩個版本FU-A,FU-B,用NAL單元類型 28,29辨別;

    NAL單元的一個分片由整數個連續NAL單元位元組組成。每個NAL單元位元組必須正好是該NAL單元一個分片的一部分。相同NAL單元的分片必須使用遞增的RTP序号連續順序發送(第一和最後分片之間沒有其他的RTP包)。相似,NAL單元必須按照RTP順序号的順序裝配。

格式如下:RTP header(12 bytes)+FU indicator(1 bytes)+FU header(1 bytes)+fu payload

FU indicator格式如下:

+---------------+

|0|1|2|3|4|5|6|7|

|F|NRI| Type |

+---------------+

F、NRI是原始NALU的前3位,Type訓示的是FU的type

FU header格式如下:

+---------------+

|0|1|2|3|4|5|6|7|

|S|E|R| Type |

+---------------+

S: 1 bit 當設定成1,開始位訓示分片NAL單元的開始。當跟随的FU荷載不是分片NAL單元荷載的開始,開始位設為0。

E: 1 bit 當設定成1, 結束位訓示分片NAL單元的結束,即, 荷載的最後位元組也是分片NAL單元的最後一個位元組。當跟随的 FU荷載不是分片NAL單元的最後分片,結束位設定為0。

R: 1 bit 保留位必須設定為0,接收者必須忽略該位

Type訓示的是NALU的type。

注意:原始的NAL頭的前三位為FU indicator的前三位,原始的NAL頭的後五位為FU header的後五位。解包時,取FU indicator的前三位和FU Header的後五位,即為NALU的首位元組。

下圖為筆者抓到的一個RTP包,前12個位元組是RTP header。5c開始是RTP資料,5c是FU indicator,41是FU header,換算為二進制:

0   是F

10 是NRI

11100 是FU type,這裡是28

0 是s

1 是E,說明是1幀的結束

0 保留,必須置0

00001 是NALU type,表示非IDR幀

vlc源碼分析(三) 調用live555接收RTP資料