live555Server讀取文件修改爲socket接收數據遇到的問題

最近在修改 live555Server端的代碼,基本需求是這樣的,live555Server有從文件讀取音視頻數據作爲 Server的 Demo。我們需要改爲 從網絡中接收音視頻數據作爲 Server的數據輸入。最終是一個程序從網絡中接收音視頻數據,然後建立 Unix socket套接字服務,等待有客戶端連接 live555Server的時候,live555Server首先創建套接字連接我們程序剛纔創建的服務,然後我們的程序向 Unix socket套接字上發送音視頻數據,live555Server接收數據作爲輸入,向 rtsp的客戶端發送數據,rtsp客戶端進行音視頻播放。
在修改過程中遇到了幾個問題,在這裏記錄一下:

1.在做的過程中出現了一點小問題。就是使用 Unix socket套接字發送數據開始的時候由於 live555Server讀取速度等原因,導致很容易將 socket緩衝區寫滿,導致寫入失敗,我們就直接把這一幀數據丟棄了。但是 live555Server向外發送 rtp包的時間戳是經過 視頻幀率、音頻採樣率與系統時間計算出來的,這就會出現一個問題。
當視頻經常丟棄時,但是音頻沒有出現緩衝區滿的問題(音頻數據量很小),rtp的時間戳計算就會出現問題,導致音視頻的時間戳相差很大,導致解碼端無法解碼顯示。
對於這個問題,我們的修改方法是 使用信源的 pts作爲 rtp的時間戳向外發送,由我們的程序向 live555Server發送音視頻數據及對應的 pts值,在 live555Server端解析出音視頻數據及 pts,並將 pts作爲 rtp的時間戳向外發送。這樣就處理了音視頻的 rtp包時間戳相差大無法解碼的問題。
其實 live555Server的 rtcp包也是使用的系統時間計算的,所以我們還需要將 rtcp的時間戳也轉換爲我們的 pts附近的值,我得處理方法是 使用上一次發送的 pts值。

2.第二個問題。在測試過程中,發現使用我們的 rtsp客戶端拉流,一般情況下 2個小時左右會出現斷流情況。原因是在 MPEGVideoStreamFramer.cpp文件的 continueReadProcessing()函數中,會調用 parse()解析音視頻函數,因爲 parse()函數在緩衝區中沒有數據的情況下,會驅動數據的讀取,然後拋出異常,然後返回0,所以這個函數的返回值會決定着下一步的走向。
當這個函數內部拋出異常時,會返回0,然後就什麼都不做。返回非0值時,會繼續驅動afterGetting(),然後打包,發送 rtp,繼續下一次定時器的安裝。
所以,可能是由於音視頻數據的原因,導致 parse函數解析正常數據後,沒有解析到有效數據,導致 parse()函數返回了0,注意:這不是拋出異常導致的。。。所以沒有進行下一次 定時器的安裝。所以,沒有驅動了。。。就會造成服務器不解析也不發送音視頻數據了。。。
我的修改方法是:在 parse()函數裏面,拋出異常導致的函數結束,返回-1(修改 parse()函數返回值類型),其他情況下繼續正常返回。continueReadProcessing()函數中在對 parse()的返回值判斷的地方,大於等於0走的是一個邏輯(繼續數據驅動)。
這樣修改過後即使因爲某次數據的解析錯誤導致 parse()函數返回了0,也可以正常去驅動下一次的數據讀取解析發送流程。

3.做測試過程中,還出現了另外一個問題,因爲我們需要傳入 pts值,所以將音視頻的 pts全部放在了 音視頻頭部的後面,數據的前面的位置。在 live555Server解析過程中會隨機的解析錯誤一次。這個問題排查了很久。最終,發現原因是在 H264or5VideoStreamFramer.cpp的 parse函數中,如果找到了下一幀的起始頭(00 00 00 01或者 00 00 01),就會跳過頭部字節並跳出循環(skipBytes()),繼續下面的處理,在函數退出之前會保存當前的解析狀態(setParseState()),但是在保存解析狀態之前,會有一個測試下一幀數據的操作,來判斷 rtp數據包打不打 Mark標誌。testBytes()這個函數在測試之後的數據的時候,如果沒有數據會向 數據輸入Source要數據,然後拋出異常,退出 parse函數。這就導致了上面 skipBytes()的數據,沒有保存解析狀態就跳出函數了,導致下一次我解析 pts值得時候,直接解析錯誤了。。。
修改方法是在 找到下一幀的起始頭之後,skipBytes()之前調用一次 setParseState()函數,保存一下當前解析狀態。

4.最後一個修改的地方是數據驅動的地方。之前代碼的邏輯是 定時器驅動數據讀取,然後在 ByteStreamFileSource.cpp裏面的 doGetNextFrame()函數裏面會添加 epoll讀感興趣事件,添加完成之後就返回,所以並沒有真正的讀取數據(也可以直接讀取數據,但是我們的 socket是非阻塞的,所以需要等到有數據時在讀取)。
然後等到 epoll讀事件觸發時,繼續上面未完成的任務,讀數據,然後調用 afterGetting()函數,一步步調用(真的繞)之後來到 MultiFramedRTPSink.cpp進行數據的發送,sendPacketIfNecessary()函數發送數據之後會繼續加一個定時器去驅動下一次的數據讀取。循環往復。
但是這裏會出現一個性能的問題,因爲這是讀文件的例子,所以它按照視頻的幀率來計算下一次的讀取時間,然後根據時間添加定時器。然而我們修改的是從網絡中獲取數據,肯定數據來的有快有慢,所以不能完全依靠定時器去驅動。如果仍然按照以前的邏輯,就會出現這個問題:
1.定時器到時間,進行數據讀取,加入 epoll讀事件,返回;
2.epoll讀事件驅動,數據讀取解析,發送 rtp數據包,繼續加下一個定時器(20ms);
3.在下一次定時器觸發之前,20ms以內,epoll讀事件觸發也沒有用,因爲數據接收的緩衝區需要定時器這邊驅動傳入 ByteStreamFileSource類,所以這 20ms以內數據來了也沒有用,只能等待,所以很容易造成 socket緩衝區滿了,導致數據發送失敗。
我的解決方法是,去掉定時器驅動,完全依靠 epoll的讀事件驅動。在 sendPacketIfNecessary()函數中,不使用
nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecondsToGo, (TaskFunc*)sendNext, this);
添加定時器,而是直接調用 sendNext函數中的有效代碼:
buildAndSendPacket(False);
直接將一切接收數據之前的操作準備好,這個時候,無論 epoll讀事件什麼時候觸發,都可以直接讀取數據,不用進行定時器的等待,這樣修改 epoll事件驅動之後,性能上有一定的提升。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章