轉自:http://xingyunbaijunwei.blog.163.com/blog/static/7653806720122212429948/
這裏主要分析一下,live555中關於RTP打包發送的部分。在處理完PLAY命令之後,就開始發送RTP數據包了(其實在發送PLAY命令的response包之前,就會發送一個RTP包,這裏傳輸就已經開始了)
RTP包的發送是從MediaSink::startPlaying函數調用開始的,應是sink跟source要數據,所以從sink上調用startplaying。
- Boolean MediaSink::startPlaying(MediaSource& source, afterPlayingFunc* afterFunc, void* afterClientData)
- {
- //參數afterFunc是在播放結束時才被調用。
- // Make sure we're not already being played:
- if (fSource != NULL) {
- envir().setResultMsg("This sink is already being played");
- return False;
- }
-
-
- // Make sure our source is compatible:
- if (!sourceIsCompatibleWithUs(source)) {
- envir().setResultMsg(
- "MediaSink::startPlaying(): source is not compatible!");
- return False;
- }
- //記下一些要使用的對象
- fSource = (FramedSource*) &source;
-
-
- fAfterFunc = afterFunc;
- fAfterClientData = afterClientData;
- return continuePlaying();
- }
|
這個函數只有最後一句最重要,即continuePlaying函數的調用。continuePlaying函數是定義在MediaSink類中的純虛函數,需要到特定媒體的sink子類中實現,對於H264來講是在H264VideoRTPSink中實現的。
H264VideoRTPSink繼承關係:H264VideoRTPSink->VideoRTPSink->MultiFramedRTPSink->RTPSink->MediaSink。
- Boolean H264VideoRTPSink::continuePlaying() {
- // First, check whether we have a 'fragmenter' class set up yet.
- // If not, create it now:
- if (fOurFragmenter == NULL) {
- //創建一個輔助類H264FUAFragmenter,用於H264的RTP打包
-
- fOurFragmenter = new H264FUAFragmenter(envir(), fSource, OutPacketBuffer::maxSize,
- ourMaxPacketSize() - 12/*RTP hdr size*/);
- fSource = fOurFragmenter;
- }
-
- // Then call the parent class's implementation:
- return MultiFramedRTPSink::continuePlaying();
- }
|
上面的代碼中創建了一個輔助類H264FUAFragmenter,因爲H264的RTP包,有些特殊需要進一步處理,可以參考RFC3986。接着調用MultiFramedRTPSink類的continuePlaying實現。
MultiFramedRTPSink是與幀有關的類,其實它要求每次必須從source獲得一個幀的數據,所以才叫這個name。可以看到continuePlaying()完全被buildAndSendPacket()代替。看一下buildAndSendPacket():
- Boolean MultiFramedRTPSink::continuePlaying() {
- // Send the first packet.
- // (This will also schedule any future sends.)
- buildAndSendPacket(True);
- return True;
- }
這時調用buildAndSendPacket函數時,看名字就知道其中將完成打包併發送工作。傳遞了一個True參數,表示這是第一個packet。繼續看buildAndSendPacket函數定義
- void MultiFramedRTPSink::buildAndSendPacket(Boolean isFirstPacket) {
- fIsFirstPacket = isFirstPacket;
- // 此函數中主要是準備rtp包的頭,爲一些需要跟據實際數據改變的字段留出位置。
- //設置RTP頭,注意,接收端需要根據RTP包的序號fSeqNo來重新排序
- //
- // Set up the RTP header:
- unsigned rtpHdr = 0x80000000; // RTP version 2; marker ('M') bit not set (by default; it can be set later)
- rtpHdr |= (fRTPPayloadType<<16);
- rtpHdr |= fSeqNo; // sequence number
- fOutBuf->enqueueWord(rtpHdr); //向包中加入一個字
-
-
- //保留一個4 bytes空間,用於設置time stamp
- // Note where the RTP timestamp will go.
- // (We can't fill this in until we start packing payload frames.)
- fTimestampPosition = fOutBuf->curPacketSize();
- fOutBuf->skipBytes(4); // leave a hole for the timestamp
-
- fOutBuf->enqueueWord(SSRC()); //跟RTCP相關
-
- //在RTP頭後面,添加一個payload-format-specific頭,
- // Allow for a special, payload-format-specific header following the
- // RTP header:
- fSpecialHeaderPosition = fOutBuf->curPacketSize();
- //
- //specialHeaderSize在MultiFramedRTPSink中的默認實現返回0,對於H264的實現不需要處理這個字段
- //
- fSpecialHeaderSize = specialHeaderSize();
- fOutBuf->skipBytes(fSpecialHeaderSize); //預留空間
-
-
- //填充儘可能多的frames到packet中
- // Begin packing as many (complete) frames into the packet as we can:
- fTotalFrameSpecificHeaderSizes = 0;
- fNoFramesLeft = False;
- fNumFramesUsedSoFar = 0; // 一個包中已打入的幀數。
//頭準備好了,再打包幀數據 - packFrame();
- }
|
buildAndSendPacket函數中,完成RTP頭的準備工作。可以看到RTP頭是非常簡單的,RTP頭中的序號非常重要,客戶端需要據此進行RTP包的重排序操作。RTP包內容存放在一個OutPacketBuffer類型的fOutBuf成員變量中,OutPacketBuffer類的細節在文章的最後還會討論。在RTP頭中預留了一些空間沒有進行實際的填充,這個工作將在doSpecialFrameHandling中進行,後面會有討論。進一步的工作,在packFrame函數中進行,它將爲RTP包填充數據。
- void MultiFramedRTPSink::packFrame()
- {
- // First, see if we have an overflow frame that was too big for the last pkt
- if (fOutBuf->haveOverflowData()) {
- //如果有幀數據,則使用之。OverflowData是指上次打包時剩下的幀數據,因爲一個包可能容納不了一個幀。
- // Use this frame before reading a new one from the source
- unsigned frameSize = fOutBuf->overflowDataSize();
- struct timeval presentationTime = fOutBuf->overflowPresentationTime();
- unsigned durationInMicroseconds =fOutBuf->overflowDurationInMicroseconds();
- fOutBuf->useOverflowData();
-
- afterGettingFrame1(frameSize, 0, presentationTime,durationInMicroseconds);
- } else {
- //一點幀數據都沒有,跟source要吧。
- // Normal case: we need to read a new frame from the source
- if (fSource == NULL)
- return;
-
- //更新緩衝中的一些位置
- fCurFrameSpecificHeaderPosition = fOutBuf->curPacketSize();
- fCurFrameSpecificHeaderSize = frameSpecificHeaderSize();
- fOutBuf->skipBytes(fCurFrameSpecificHeaderSize);
- fTotalFrameSpecificHeaderSizes += fCurFrameSpecificHeaderSize;
-
- //從source獲取下一幀
- fSource->getNextFrame(fOutBuf->curPtr(),//新數據存放開始的位置
- fOutBuf->totalBytesAvailable(),//緩衝中空餘的空間大小
- afterGettingFrame, //因爲可能source中的讀數據函數會被放在任務調度中,所以把獲取幀後應調用的函數傳給source
- this,
- ourHandleClosure, //這個是source結束時(比如文件讀完了)要調用的函數。
- this);
- }
- }
|
packFrame函數需要處理兩種情況:
1).buffer中存在未發送的數據(overflow data),這時可以將調用afterGettingFrame1函數進行後續處理工作。
2).buffer不存在數據,這時需要調用source上的getNextFrame函數獲取數據。getNextFrame調用時,參數中有兩個回調用函數:afterGettingFrame函數將在獲取到數據後調用,其中只是簡單的調用了afterGettingFrame1函數而已,這是因爲C++中是不充許類成員函數作爲回調用函數的;ourHandleClosure函數將在數據已經處理完畢時調用,如文件結束。
可以想像下面就是source從文件(或某個設備)中讀取一幀數據,讀完後返回給sink,當然不是從函數返回了,而是以調用afterGettingFrame這個回調函數的方式。所以下面看一下afterGettingFrame():
- void MultiFramedRTPSink::afterGettingFrame(void* clientData,
- unsigned numBytesRead, unsigned numTruncatedBytes,
- struct timeval presentationTime, unsigned durationInMicroseconds)
- {
- MultiFramedRTPSink* sink = (MultiFramedRTPSink*) clientData;
- sink->afterGettingFrame1(numBytesRead, numTruncatedBytes, presentationTime,
- durationInMicroseconds);
- }
|
可以看出,它只是過度爲調用成員函數,所以afterGettingFrame1()纔是重點:
- void MultiFramedRTPSink::afterGettingFrame1(
- unsigned frameSize,
- unsigned numTruncatedBytes,
- struct timeval presentationTime,
- unsigned durationInMicroseconds)
- {
- if (fIsFirstPacket) {
- // Record the fact that we're starting to play now: 第一個packet,則記錄下當前時間
- gettimeofday(&fNextSendTime, NULL);
- }
-
- //這裏的處理要注意了,當一個Frame大於OutPacketBuffer::maxSize(默認值爲60000)時,則會丟棄剩下的部分,numTruncatedBytes即爲超出部分的大小。
- //如果給予一幀的緩衝不夠大,就會發生截斷一幀數據的現象。但也只能提示一下用戶
- if (numTruncatedBytes > 0) {
-
- unsigned const bufferSize = fOutBuf->totalBytesAvailable();
- envir()
- << "MultiFramedRTPSink::afterGettingFrame1(): The input frame data was too large for our buffer size ("
- << bufferSize
- << "). "
- << numTruncatedBytes
- << " bytes of trailing data was dropped! Correct this by increasing \"OutPacketBuffer::maxSize\" to at least "
- << OutPacketBuffer::maxSize + numTruncatedBytes
- << ", *before* creating this 'RTPSink'. (Current value is "
- << OutPacketBuffer::maxSize << ".)\n";
- }
- unsigned curFragmentationOffset = fCurFragmentationOffset;
- unsigned numFrameBytesToUse = frameSize;
- unsigned overflowBytes = 0;
-
-
- //如果包只已經打入幀數據了,並且不能再向這個包中加數據了,則把新獲得的幀數據保存下來。
- // If we have already packed one or more frames into this packet,
- // check whether this new frame is eligible to be packed after them.
- // (This is independent of whether the packet has enough room for this
- // new frame; that check comes later.)
- // fNumFramesUsedSoFar>0 表示packet已經存在frame,需要檢查是否充許在packet中加入新的frame
- if (fNumFramesUsedSoFar > 0) {
- //如果包中已有了一個幀,並且不允許再打入新的幀了,則只記錄下新的幀。
- if ((fPreviousFrameEndedFragmentation
- && !allowOtherFramesAfterLastFragment()) //不充許在前一個分片之後,跟隨一個frame
- || !frameCanAppearAfterPacketStart(fOutBuf->curPtr(), frameSize)) //frame不能出現在非packet的開始位置
- {
- // Save away this frame for next time:
- numFrameBytesToUse = 0;
- //不充許添加新的frame,則保存爲溢出數據,下次處理
- fOutBuf->setOverflowData(fOutBuf->curPacketSize(), frameSize,
- presentationTime, durationInMicroseconds);
- }
- }
-
- //表示當前打入的是否是上一個幀的最後一塊數據。
- fPreviousFrameEndedFragmentation = False;
-
-
- //下面是計算獲取的幀中有多少數據可以打到當前包中,剩下的數據就作爲overflow數據保存下來。
- if (numFrameBytesToUse > 0) {
- // Check whether this frame overflows the packet
- if (fOutBuf->wouldOverflow(frameSize)) {
- // Don't use this frame now; instead, save it as overflow data, and
- // send it in the next packet instead. However, if the frame is too
- // big to fit in a packet by itself, then we need to fragment it (and
- // use some of it in this packet, if the payload format permits this.)
- //若frame將導致packet溢出,應該將其保存到packet的溢出數據中,在下一個packet中發送。
//如果frame本身大於pakcet 的max size, 就需要對frame進行分片操作。不過需要調用allowFragmentationAfterStart 函數以確定是否充許分片 - if (isTooBigForAPacket(frameSize)
- && (fNumFramesUsedSoFar == 0 || allowFragmentationAfterStart())) {
- // We need to fragment this frame, and use some of it now:
- overflowBytes = computeOverflowForNewFrame(frameSize);
- numFrameBytesToUse -= overflowBytes;
- fCurFragmentationOffset += numFrameBytesToUse;
- } else {
- // We don't use any of this frame now:
- overflowBytes = frameSize;
- numFrameBytesToUse = 0;
- }
- fOutBuf->setOverflowData(fOutBuf->curPacketSize() + numFrameBytesToUse,
- overflowBytes, presentationTime, durationInMicroseconds);
- } else if (fCurFragmentationOffset > 0) {
- // This is the last fragment of a frame that was fragmented over
- // more than one packet. Do any special handling for this case:
- fCurFragmentationOffset = 0;
- fPreviousFrameEndedFragmentation = True;
- }
- }
-
-
- if (numFrameBytesToUse == 0 && frameSize > 0) {
- //如果包中有數據並且沒有新數據了,則發送之。(這種情況好像很難發生啊!)
- // Send our packet now, because we have filled it up:
- sendPacketIfNecessary();
- } else {
- //需要向包中打入數據。
-
- // Use this frame in our outgoing packet:
- unsigned char* frameStart = fOutBuf->curPtr();
- fOutBuf->increment(numFrameBytesToUse);
- // do this now, in case "doSpecialFrameHandling()" calls "setFramePadding()" to append padding bytes
-
- //還記得RTP頭中序留的空間嗎,將在這個函數中進行填充
- // Here's where any payload format specific processing gets done:
- doSpecialFrameHandling(curFragmentationOffset, frameStart,
- numFrameBytesToUse, presentationTime, overflowBytes);
-
-
- ++fNumFramesUsedSoFar;
-
- //設置下一個packet的時間信息,這裏若存在overflow數據,就不需要更新時間,因爲這是同一個frame的不同分片,需要保證時間一致
- // Update the time at which the next packet should be sent, based
- // on the duration of the frame that we just packed into it.
- // However, if this frame has overflow data remaining, then don't
- // count its duration yet.
- if (overflowBytes == 0) {
- fNextSendTime.tv_usec += durationInMicroseconds;
- fNextSendTime.tv_sec += fNextSendTime.tv_usec / 1000000;
- fNextSendTime.tv_usec %= 1000000;
- }
-
-
- //如果需要,就發出包,否則繼續打入數據。
- // Send our packet now if (i) it's already at our preferred size, or
- // (ii) (heuristic) another frame of the same size as the one we just
- // read would overflow the packet, or
- // (iii) it contains the last fragment of a fragmented frame, and we
- // don't allow anything else to follow this or
- // (iv) one frame per packet is allowed:
- if (fOutBuf->isPreferredSize()
- || fOutBuf->wouldOverflow(numFrameBytesToUse)
- || (fPreviousFrameEndedFragmentation
- && !allowOtherFramesAfterLastFragment())
- || !frameCanAppearAfterPacketStart(
- fOutBuf->curPtr() - frameSize, frameSize)) {
- // The packet is ready to be sent now
- sendPacketIfNecessary(); //發送RTP包
- } else {
- // There's room for more frames; try getting another:
- packFrame(); //packet中還可以容納frame,這裏將形成遞歸調用
- }
- }
- }
|
afterGettingFrame1的複雜之處在於處理frame的分片,若一個frame大於TCP/UDP有效載荷(程序中定義爲1448個字節),就必需分片了。最簡單的情況就是一個packet(RTP包)中最多隻充許一個frame,即一個RTP包中存在一個frame或者frame的一個分片,H264就是這樣處理的。,方法是將剩餘的數據記錄爲buffer的溢出部分。下次調用packFrame函數時,直接從溢出部分複製到packet中。不過應該注意,一個frame的大小不能超過buffer的大小(默認爲60000),否則會真的溢出,
那就應該考慮增加buffer大小了。
上面的代碼中還調用了doSpecialFrameHandling,子類需要重新實現進行一些特殊處理,文章最後還會討論這個問題。
在packet中充許出現多個frame的情況下(大多數情況下應該沒必要用到),採用了遞歸來實現,可以看到afterGettingFrame1函數的最後有調用packFrame的代碼。
再來看RTP的發送函數sendPacketIfNecessary
- void MultiFramedRTPSink::sendPacketIfNecessary() {
- //
- //packet中存在frame,則發送出去
- //
- if (fNumFramesUsedSoFar > 0) {
- // Send the packet:
- //
- //可以通過TEST_LOSS宏,模擬10%丟包
- //
- #ifdef TEST_LOSS
- if ((our_random()%10) != 0) // simulate 10% packet loss #####
- #endif
- //
- //現在通過調用RTPInterface::sendPacket函數發送packet
- //
- if (!fRTPInterface.sendPacket(fOutBuf->packet(), fOutBuf->curPacketSize())) {
- // if failure handler has been specified, call it
- if (fOnSendErrorFunc != NULL) (*fOnSendErrorFunc)(fOnSendErrorData); //錯誤處理
- }
- ++fPacketCount;
- fTotalOctetCount += fOutBuf->curPacketSize();
- fOctetCount += fOutBuf->curPacketSize()
- - rtpHeaderSize - fSpecialHeaderSize - fTotalFrameSpecificHeaderSizes;
-
-
- ++fSeqNo; // for next time
- }
-
- //如果還有剩餘數據,則調整緩衝區
- if (fOutBuf->haveOverflowData()
- && fOutBuf->totalBytesAvailable() > fOutBuf->totalBufferSize()/2) {
- //
- //爲了提高效率,可以直接重置buffer中的packet開始位置,這樣就不需要拷貝一遍overflow數據了。
- //在一個packet只能包含一個frame的情況下,是不是可以考慮修改這裏的判斷條件呢?
- //
- // Efficiency hack: Reset the packet start pointer to just in front of
- // the overflow data (allowing for the RTP header and special headers),
- // so that we probably don't have to "memmove()" the overflow data
- // into place when building the next packet:
- unsigned newPacketStart = fOutBuf->curPacketSize()
- - (rtpHeaderSize + fSpecialHeaderSize + frameSpecificHeaderSize());
- fOutBuf->adjustPacketStart(newPacketStart); //調整buffer中的packet 開始位置
- } else {
- // Normal case: Reset the packet start pointer back to the start:
- fOutBuf->resetPacketStart(); //這種情況,若存在overflow data,就需要進行copy操作了
- }
- fOutBuf->resetOffset(); //packet已經發送了,可以重置buffer中的數據offset了
- fNumFramesUsedSoFar = 0; //清零packet中的frame數
- //
- //數據已經發送完畢(例如文件傳輸完畢),可以關閉了
- //
- if (fNoFramesLeft) {
- // We're done:
- onSourceClosure(this);
- } else { //如果還有數據,則在下一次需要發送的時間再次打包發送
- //
- //準備下一次發送任務
- //
- // We have more frames left to send. Figure out when the next frame
- // is due to start playing, then make sure that we wait this long before
- // sending the next packet.
- struct timeval timeNow;
- gettimeofday(&timeNow, NULL);
- int secsDiff = fNextSendTime.tv_sec - timeNow.tv_sec; //若是同一個frame的不同分片,這個值將爲0
- int64_t uSecondsToGo = secsDiff*1000000 + (fNextSendTime.tv_usec - timeNow.tv_usec);
- if (uSecondsToGo < 0 || secsDiff < 0) { // sanity check: Make sure that the time-to-delay is non-negative:
- uSecondsToGo = 0;
- }
- //
- //作延時時間,處理函數,將入到任務調試器中,以便進行下一次發送操作
- //
- // Delay this amount of time:
- nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecondsToGo, (TaskFunc*)sendNext, this);
- }
- }
|
sendPacketIfNecessary函數處理一些發送的細節,我們來看最重要的兩點。
1)RTP包還是轉交給了RTPInterface::sendPacket函數,等下再看其具體實現。
2)將下一次RTP的發送操作加入到任務調度器中,參數中傳遞了sendNext函數指針,其實現比較簡單,如下
- void MultiFramedRTPSink::sendNext(void* firstArg) {
- MultiFramedRTPSink* sink = (MultiFramedRTPSink*)firstArg;
- sink->buildAndSendPacket(False); //現在已經不是第一次調用了
- }
可以看到爲了延遲包的發送,使用了delay task來執行下次打包發送任務。sendNext()中又調用了buildAndSendPacket()函數,輪迴了。。。
總結一下調用過程:
總結一下調用過程:
最後,再說明一下包緩衝區的使用:
MultiFramedRTPSink中的幀數據和包緩衝區共用一個,只是用一些額外的變量指明緩衝區中屬於包的部分以及屬於幀數據的部分(包以外的數據叫做overflow data)。它有時會把overflow data以mem move的方式移到包開始的位置,有時把包的開始位置直接設置到overflow data開始的地方。那麼這個緩衝的大小是怎樣確定的呢?是跟據調用者指定的的一個最大的包的大小+60000算出的。這個地方把我搞胡塗了:如果一次從source獲取一個幀的話,那這個緩衝應設爲不小於最大的一個幀的大小纔是,爲何是按包的大小設置呢?可以看到,當緩衝不夠時只是提示一下.當然此時不會出錯,但有可能導致時間戳計算不準,或增加時間戳計算與source端處理的複雜性(因爲一次取一幀時間戳是很好計算的)。
現在來看RTPInterface::sendPacket函數
- Boolean RTPInterface::sendPacket(unsigned char* packet, unsigned packetSize) {
- Boolean success = True; // we'll return False instead if any of the sends fail
-
- //一般情況下,使用UDP發送
- // Normal case: Send as a UDP packet:
- if (!fGS->output(envir(), fGS->ttl(), packet, packetSize)) success = False;
-
- //使用TCP發送
- // Also, send over each of our TCP sockets:
- for (tcpStreamRecord* streams = fTCPStreams; streams != NULL;
- streams = streams->fNext) {
- if (!sendRTPOverTCP(packet, packetSize,
- streams->fStreamSocketNum, streams->fStreamChannelId)) {
- success = False;
- }
- }
-
-
- return success;
- }
若是使用UDP方式發送,將調用Groupsock::output函數,可以實現組播功能。groupsock只實現了UDP發送功能,當用TCP方式傳送時調用sendRTPOverTcP函數,這個函數中直接調用socket的send函數。
現在RTP的發送終於結束了,groupsock的實現留待下次分析。現在再來看一個遺留的問題,MultiFramedRTPSink::doSpecialFrameHandling的實現。它是定義在MultiFramedRTPSink中的虛函數,先來看其默認的實現.
- void MultiFramedRTPSink::doSpecialFrameHandling(unsigned /*fragmentationOffset*/,
- unsigned char* /*frameStart*/,
- unsigned /*numBytesInFrame*/,
- struct timeval framePresentationTime,
- unsigned /*numRemainingBytes*/) {
- // default implementation: If this is the first frame in the packet,
- // use its presentationTime for the RTP timestamp:
- if (isFirstFrameInPacket()) {
- setTimestamp(framePresentationTime);
- }
- }
可以看到默認實現中只是在第一次調用時,設置RTP包中的的時間信息,下面來看H264VideoRTPSink上的實現
- void H264VideoRTPSink::doSpecialFrameHandling(unsigned /*fragmentationOffset*/,
- unsigned char* /*frameStart*/,
- unsigned /*numBytesInFrame*/,
- struct timeval framePresentationTime,
- unsigned /*numRemainingBytes*/) {
- //
- //設置RTP頭中的M位
- //
- // Set the RTP 'M' (marker) bit iff
- // 1/ The most recently delivered fragment was the end of (or the only fragment of) an NAL unit, and
- // 2/ This NAL unit was the last NAL unit of an 'access unit' (i.e. video frame).
- if (fOurFragmenter != NULL) {
- H264VideoStreamFramer* framerSource
- = (H264VideoStreamFramer*)(fOurFragmenter->inputSource());
- // This relies on our fragmenter's source being a "H264VideoStreamFramer".
- if (fOurFragmenter->lastFragmentCompletedNALUnit()
- && framerSource != NULL && framerSource->pictureEndMarker()) {
- setMarkerBit();
- framerSource->pictureEndMarker() = False;
- }
- }
- //
- //設置時間戳
- //
- setTimestamp(framePresentationTime);
- }
解釋一下RTP頭中的M位,H264圖像可能被封裝成多個NALU,這些NALU就擁有相同的同時間。根據RFC3984的定義,當RTP中封裝的是同一圖像的最後一個NALU時,就需要設置M位