live555 數據是怎麼讀取傳輸的,下面一起來看看。
live 的發送過程
以 H264 編碼格式發送爲例,主要操作流程如下:
- H264or5VideoRTPSink::continuePlaying() 在該函數中將開始數據的傳輸。
先創建 H264or5Fragmenter 對象,它將讀取的數據按照rtsp協議分段發送出去,而後將 H264or5Fragmenter 賦值到 fSource。 - MultiFramedRTPSink::continuePlaying(),在該函數中,調用 buildAndSendPacket(True),最終調用到 MultiFramedRTPSink::packFrame(),而後通過 fSource->getNextFrame() 調用到 FramedSource::getNextFrame() 函數,從而調用到虛函數 doGetNextFrame()。
- 由於 fSource 賦值爲 H264or5Fragmenter,所以將會通過 FramedSource::getNextFrame() 調用到 H264or5Fragmenter::doGetNextFrame() 函數。而是第一次調用該函數,fNumValidDataBytes變量在構造函數中通過reset() 函數復位置 1,所以將調用 fInputSource->getNextFrame()。
- 上面的 fInputSource 賦值爲什麼呢?
在運行RTSP server前,將會通過 addServerMediaSession() 添加流信息到server,而一般流信息中都將從 FramedFilter 類繼承下來的。H264 的是 H264VideoStreamFramer 類,所以上面的 fInputSource 賦值爲 H264VideoStreamFramer 對象,通過 FramedSource::getNextFrame() 在fInputSource->getNextFrame()這裏,將會調用到 H264VideoStreamFramer 的父類 (H264or5VideoStreamFramer —> MPEGVideoStreamFramer —> FramedFilter) MPEGVideoStreamFramer 中的 doGetNextFrame() 函數。 - MPEGVideoStreamFramer::doGetNextFrame()函數,該函數主要是調用 MPEGVideoStreamFramer::continueReadProcessing() 函數完成一些操作。
在 MPEGVideoStreamFramer::continueReadProcessing() 函數中,將會通過 fParser->parse() 讀取數據,由於 parse() 是虛函數,H264格式將會調用到 H264or5VideoStreamParser::parse() 函數。在該函數中,將會通過以下類似的接口讀取數據(test4Bytes() —> ensureValidBytes() —> ensureValidBytes1() —> fInputSource->getNextFrame())。這裏的 fInputSource->getNextFrame()。這裏的 fInputSource 是在創建 H264or5VideoStreamFramer 時,根據傳遞下來 inputSource 創建了 H264or5VideoStreamParser 實例對象保存在 fParser。所以這裏將會調用到創建 H264VideoStreamFramer 對象時傳遞的 inputsource 讀取數據。(H264VideoStreamFramer 對象在註冊rtsp server相關的 ServerMediaSession 時的 addSubsession 對象的 createNewStreamSource() 函數創建) - 按照上述說的,最終將會在 ensureValidBytes1() 函數中讀取數據,該函數並不是按照上面傳遞的參數大小讀取數據,而是會盡可能多的讀取數據,在 StreamParser::ensureValidBytes1() 中,fTotNumValidBytes 代表bank中已經保存了流數據的字節數,fCurParserIndex 代表當前解析流的位置,數據先讀取再解析。讀取完成之後,將會調用 StreamParser::afterGettingBytes() 函數(該函數主要是 使用了 source->fFrameSize 和 source->fPresentationTime),最後將調用 fClientContinueFunc(fClientContinueClientData, ptr, numBytesRead, presentationTime) 函數。
- (H264or5VideoStreamParser —> MPEGVideoStreamParser —> StreamParser) StreamParser 的衍生如上,可以瞭解到 fClientContinueFunc(fClientContinueClientData, ptr, numBytesRead, presentationTime) 最終會調用 MPEGVideoStreamFramer::continueReadProcessing()。會發現,再次回到了這個函數,由於第一次調用parse() 時,我們沒有緩衝數據,所以最終執行 ensureValidBytes1() 函數讀取,當前已經獲取到數據了,我們將直接解析該數據。
- 在 H264or5VideoStreamParser::parse() 函數按照 H264 格式解析數據之後,將返回解析成功的數據大小,MPEGVideoStreamParser.fTo 爲記錄當前的發送寫入數據指針,MPEGVideoStreamParser.fLimit 爲緩衝隊列總大小。然後返回到 MPEGVideoStreamFramer::continueReadProcessing() 函數。在該函數中,將根據解析的情況,設置 fFrameSize、fNumTruncatedBytes 和 fDurationInMicroseconds,最後調用 afterGetting(this)。所以沒什麼有時候在其他位置的 afterGetting() 函數確認 fFrameSize 等值變化了,就是在這裏變化的,它們需要從流解析之後再調用 afterGetting()。
- 前一步的 afterGetting() 經過 FramedSource::afterGetting() 函數之後,將調用到 H264or5Fragmenter::afterGettingFrame(),在該函數中,frameSize 等變量都已經發生改變,最後調用到 H264or5Fragmenter::doGetNextFrame()。只是因爲這一次 fNumValidDataBytes = frameSize,所以將會和第一次調用該函數時不一致,不再調用 fInputSource->getNextFrame() 而是進行相應的數據解析。
- 繼續以發送H264編碼格式爲例進行介紹,在經過 H264or5VideoStreamParser::parse() 解析之後,第一次將會返回 10 個字節的數據,這些是H264的SPS信息,所以在 H264or5Fragmenter::doGetNextFrame()中,只是將 fInputBuffer數據拷貝到 fTo, 然後調用 FramedSource::afterGetting(this) 函數。這裏的 afterGetting(),由於是在 MultiFramedRTPSink::packFrame() 函數中通過 fSource->getNextFrame() 調用的,所以 afterGetting() 將調用到 MultiFramedRTPSink::afterGettingFrame() 。
- 在 MultiFramedRTPSink::afterGettingFrame() 中,由於是數據流的最開始部分,沒有溢出,也沒有之前的幀數據,所以最後只是
// Use this frame in our outgoing packet:
,fNumFramesUsedSoFar自加。由於 H264 的 H264or5VideoRTPSink::frameCanAppearAfterPacketStart() 返回的是 false,所以將會調用 sendPacketIfNecessary() 發送數據。此時,數據將發送出去,最開始的 H264or5VideoRTPSink::continuePlaying() 執行完畢。 - 那麼,上述只是發送了一次數據而已,後續又是怎樣進行相應的數據讀取呢,我們先來看看 MultiFramedRTPSink::sendPacketIfNecessary() 的具體實現。在該函數中通過調用 fRTPInterface.sendPacket(fOutBuf->packet(), fOutBuf->curPacketSize()) 完成數據的發送,最後又會調用 nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecondsToGo, (TaskFunc*)sendNext, this)。延時調用 sendNext() 函數,而 sendNext() 函數就是簡單的調用了 sink->buildAndSendPacket(False)。從這裏就可以看到,又通過 buildAndSendPacket() 函數打包封裝數據進行下一次發送。
第二次調用 sink->buildAndSendPacket(False) 函數調用流程(下一個rtp包數據):
- packFrame() —> 由於第一次發送包時,只是解析SPS信息,所以前一個包並沒有溢出,所以可以繼續調用 fSource->getNextFrame() (fSource 爲 H264or5Fragmenter 對象) —> H264or5Fragmenter::doGetNextFrame();
- 又回到 H264or5Fragmenter::doGetNextFrame() 函數,由於第一次是的時候,在 H264or5Fragmenter::doGetNextFrame() 中進行了數據的解析,當時 fCurDataOffset == 1 且 fNumValidDataBytes - 1 <= fMaxSize,所以最後 fNumValidDataBytes = fCurDataOffset = 1,所以這次繼續還是 fNumValidDataBytes == 1,將調用 fInputSource->getNextFrame() (fInputSource 爲 MPEGVideoStreamFramer 對象) —> MPEGVideoStreamFramer::doGetNextFrame()。
- 在 MPEGVideoStreamFramer::doGetNextFrame() 函數中最終又是經過parse後(由於第一次已經讀取了很多數據,而第一次解析沒有解析完,所以這次沒有真正的從文件或者其他方式獲取數據而是繼續解析之前殘留的),將解析完成的數據通過 afterGetting(this) 返回,將調用到 H264or5Fragmenter::afterGettingFrame()。
- 在 H264or5Fragmenter::afterGettingFrame() 函數中,還是按照第一次的方式進行解析,然後通過 afterGetting(this) 返回,將調用到 MultiFramedRTPSink::afterGettingFrame(),直到最後又完成了一次 buildAndSendPacket()。
live發送過程,數據是怎麼傳遞的?
- 數據是怎麼來的?
從最開始的文件讀取開始,在 ByteStreamFileSource::doReadFromFile() 函數中,將會通過 read() 函數從磁盤中讀取到數據保存在 fTo 指針指向的內存,而讀取的大小爲 fFrameSize,最大可讀取大小爲 fMaxSize,實際上fMaxSize可爲 300000 個字節,下一步將會知道該值的大小。同時,將會在這裏獲取得到一個時間戳。 - 保存數據的內存是哪裏來的?
在上面我們已經知道,是在 StreamParser::ensureValidBytes1() 中調用相應輸入源的 getNextFrame() 函數,所以,在 ByteStreamFileSource::doReadFromFile() 中的參數都是這裏賦值的。
來看看 StreamParser 類是怎麼管理數據的。
class StreamParser {
/* 在創建StreamParser對象時,將會創建兩個 BANK_SIZE 大小的內存空間,
* 並將地址保存在指針數組fBank,BANK_SIZE 默認大小爲300000。
*/
// Use a pair of 'banks', and swap between them as they fill up:
unsigned char* fBank[2];
unsigned char fCurBankNum;
unsigned char* fCurBank;
/* fSavedParserIndex 用於保存 fCurParserIndex 的值 */
// The most recent 'saved' parse position:
unsigned fSavedParserIndex; // <= fCurParserIndex
unsigned char fSavedRemainingUnparsedBits;
/* fCurParserIndex 指向已經讀取到的數據被解析的數組內容索引 */
// The current position of the parser within the current bank:
unsigned fCurParserIndex; // <= fTotNumValidBytes
unsigned char fRemainingUnparsedBits; // in previous byte: [0,7]
/* fTotNumValidBytes 指向fBank已寫的數組索引 */
// The total number of valid bytes stored in the current bank:
unsigned fTotNumValidBytes; // <= BANK_SIZE
// Whether we have seen EOF on the input source:
Boolean fHaveSeenEOF;
struct timeval fLastSeenPresentationTime; // hack used for EOF handling
};
在 StreamParser::ensureValidBytes1() 函數中,將盡可能多的讀取流數據。當fCurBank 指向的內存空間不能保存 numBytesNeeded 大小的內容時,將會將把 fSavedParserIndex —> fTotNumValidBytes 處的數據拷貝到新的bank數組,然後再使用該bank進行內容讀取(爲什麼是 fSavedParserIndex 而不是 fCurParserIndex,因爲當前雖然已經解析到 fCurParserIndex,但是真正確認的只是到 fSavedParserIndex,所以是從 fSavedParserIndex 開始,拷貝之後再更新相應變量的值)。讀取到數據之後,將會通過 fClientContinueFunc(fClientContinueClientData, ptr, numBytesRead, presentationTime) 返回上層,此時將調用到 MPEGVideoStreamFramer::continueReadProcessing(),注意,這裏初始在 fClientContinueFunc() 中傳遞下來的函數參數並沒有使用。
- MPEGVideoStreamFramer::continueReadProcessing() 又是怎麼處理的?
在該函數中主要是在 H264or5VideoStreamParser::parse() 函數解析數據流,這些數據流就是 StreamParser::ensureValidBytes1() 讀取的。H264or5VideoStreamParser::parse() 主要進行以下操作:
- 尋找H264碼流每幀的開始碼(0x00000001),將會一直尋找,直到找到了纔會往下走。
- 尋找H264碼流的NALUnit單元,根據不同的 NAL 類型進行相應的拷貝(VPS—>fLastSeenVPS、SPS—>fLastSeenSPS、PPS—>fLastSeenPPS)
- StreamParser 對象中的bank數據是怎麼傳遞上去呢?
首先我們看看 MPEGVideoStreamParser 類。
class MPEGVideoStreamParser: public StreamParser {
/* 在MPEGVideoStreamFramer::doGetNextFrame()中,通過 registerReadInterest()
* 把FramedSource->fTo 賦值給MPEGVideoStreamParser的以下變量,需要注意的是,
* 這裏的 FramedSource->fTo 是在 H264or5Fragmenter::doGetNextFrame() 中通過
* getNextFrame() 函數賦值的,所以 fTo = H264or5Fragmenter->fInputBuffer。
* 在 MPEGVideoStreamFramer::doGetNextFrame() 中,先 registerReadInterest()
* 再有相應的parse()
*/
/* fStartOfFrame 每幀的開始位置,在registerReadInterest() 賦值後就不變化 */
unsigned char* fStartOfFrame;
unsigned char* fTo;
unsigned char* fLimit;
unsigned fNumTruncatedBytes;
}
數據拷貝到哪裏我們已經知道了,然後又是在哪裏拷貝的呢,我們看看parse函數。
unsigned H264or5VideoStreamParser::parse() {
/* 尋找H264碼流的開始碼0x00000001 */
...
/* 獲取NAL類型 */
u_int32_t next4Bytes = test4Bytes();
if (!fHaveSeenFirstByteOfNALUnit) {
fFirstByteOfNALUnit = next4Bytes>>24;
fHaveSeenFirstByteOfNALUnit = True;
}
/* 這裏就從 bank 數據通過 test4Bytes() 函數讀取出來,然後再判斷,
* 是否是開始碼(0x00000001)或者NAL碼(0x000001),如果不是,那麼就
* 是編碼數據,將保存到fTo
*/
while (next4Bytes != 0x00000001 && (next4Bytes&0xFFFFFF00) != 0x00000100) {
// We save at least some of "next4Bytes".
/* 這裏做一個判斷,如果這四個字節中,最低字節是大於1的,那麼,這4個字節都是編碼數據,直接保存 */
if ((unsigned)(next4Bytes&0xFF) > 1) {
// Common case: 0x00000001 or 0x000001 definitely doesn't begin anywhere in "next4Bytes", so we save all of it:
save4Bytes(next4Bytes);
skipBytes(4);
} else { /* 否則的話,只是保存高字節:因爲最低字節是小於或這等於1的,數據可能是這種類型0x80000001,
* 也就是可能保存NAL碼,但是可以確保的是,高字節一定是編碼數據,所以這裏就只保存高字節
*/
// Save the first byte, and continue testing the rest:
saveByte(next4Bytes>>24);
skipBytes(1);
}
setParseState(); // ensures forward progress
next4Bytes = test4Bytes();
}
}
在上述的那個while循環時需要注意,在通過 test4Bytes() 讀取數據時,很有可能又會讀取磁盤數據保存在bank,讀完之後,又將是重新進入parse() 函數。
在調用完成parse()之後,數據已經從 StreamParser 的 bank 數組拷貝到 H264or5Fragmenter->fInputBuffer 中,其中,最多可以拷貝 OutPacketBuffer::maxSize 個字節,拷貝了一個NAL單元的數據後,返回到 MPEGVideoStreamFramer::continueReadProcessing() 函數,接着又是 afterGetting(this) 函數的調用,這個時候,將通過 fAfterGettingFunc() 函數指針調用到 H264or5Fragmenter::afterGettingFrame() 函數。
-
在 H264or5Fragmenter::afterGettingFrame() 函數中,最終又將調用到 H264or5Fragmenter::doGetNextFrame() 函數。在 H264or5Fragmenter::doGetNextFrame() 函數中,將會根據將要發送的該幀數據之前是否有發送過,以及一個包是否可能填充滿等進行相應的數據拷貝,數據將從 H264or5Fragmenter->fInputBuffer 拷貝到 FramedSource->fTo,然後調用 FramedSource::afterGetting(this) 函數。
-
在這裏,將通過 FramedSource::afterGetting(this) 調用到 MultiFramedRTPSink::afterGettingFrame() 函數,同時,在 H264or5Fragmenter::doGetNextFrame() 中的 FramedSource->fTo 是在 MultiFramedRTPSink::packFrame() 中填充的 fOutBuf->curPtr(),所以,當前數據拷貝到了 fOutBuf->curPtr(),此處最多可以拷貝 RTP_PAYLOAD_MAX_SIZE (1456) 個字節。我們繼續分析 MultiFramedRTPSink::afterGettingFrame() 函數。在該函數中,將進行一些解析,判斷當前幀是否會溢出等,接着調用 sendPacketIfNecessary() 函數,在這裏調用 fRTPInterface.sendPacket(fOutBuf->packet(), fOutBuf->curPacketSize()) 通過socket發送數據,最後延時調用 sendNext() 函數,繼續下一次的發送流程。