首先了解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中,堆棧如下圖所示:

第二件事是讀取的BufferedPacket,進行一系列拆包操作後,将資料放入資料fifo中,堆棧如下圖所示:
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幀