轉自:http://blog.csdn.net/kof98765/article/details/17733701
1 音視頻實時傳輸
1.1 Jrtplib庫介紹
本系統采用開源庫Jrtplib進行RTP傳輸模塊的開發。Jrtplib庫是由比利時Hasselt大學EDM(Expertise Centre for Digital Media)開發的一個用C++語言實現的完全開源的RTP庫,目前已經可以運行在Windows、Linux、FreeBSD、Solaris、Unix和VxWorks等多種操作系統上,其現有的最新版本為Jrtplib3.7.1。
Jrtplib完全遵循RFC3550標准設計,提供了豐富的接口,使用win32 socket實現網絡通訊。應用該庫進行開發設計時,開發人員只需要關心RTP負載、RTCP負載、時間戳增量、發送目標地址及端口、RTCP帶寬及發送間隔等最基本的問題,而不必考慮一些瑣碎的細節,可以從Jrtplib的網站(http://lumumba.luc.ac.be/jori/jrtplib/jrtplib.html)下載更新的源碼包。
1.2 Jrtplib初始化
在使用Jrtplib進行實時流媒體數據傳輸之前,首先應該生成RTPSession類的一個實例來表示此次RTP會話,然后調用Create() 方法來對其進行初始化操作。RTPSession類的Create()方法只有一個參數,用來指明此次RTP會話所采用的端口號。示例1中給出了一個最簡單的初始化框架,它只是完成了RTP會話的初始化工作,還不具備任何實際的功能。
1、initial.cpp
#include "rtpsession.h"
int main(void)
{
RTPSession sess;
sess.Create(5000);
return 0;
}
如果RTP會話創建過程失敗,Create()方法將會返回一個負數,通過它雖然可以很容易地判斷出函數調用究竟是成功的還是失敗的,但卻很難明白出錯的原因到底什么。Jrtplib采用了統一的錯誤處理機制,它提供的所有函數如果返回負數就表明出現了某種形式的錯誤,而具體的出錯信息則可以通過調用 RTPGetErrorString()函數得到。RTPGetErrorString()函數將錯誤代碼作為參數傳入,然后返回該錯誤代碼所對應的錯誤信息。示例2給出了一個更加完整的初始化框架,它可以對RTP會話初始化過程中所產生的錯誤進行更好的處理:
2、framework.cpp
#include
#include "rtpsession.h"
int main(void)
{
RTPSession sess;
int status;
char* msg;
sess.Create(6000);
msg = RTPGetErrorString(status);
printf("Error String:%s\\n",msg);
return 0;
}
設置恰當的時戳單元,是RTP會話初始化過程所要進行的另外一項重要工作,這是通過調用RTPSession類的SetTimestampUnit ()方法來實現的,該方法同樣也只有一個參數,表示的是以秒為單元的時戳單元。例如,當使用RTP會話傳輸8000Hz采樣的音頻數據時,由於時戳每秒鍾將遞增8000,所以時戳單元相應地應該被設置成1/8000:
sess.SetTimestampUnit(1.0/8000.0);
1.3 Jrtplib數據發送
當RTP會話成功建立起來之后,接下去就可以開始進行流媒體數據的實時傳輸了。首先需要設置好數據發送的目標地址,RTP協議允許同一會話存在多個目標地址,這可以通過調用RTPSession類的AddDestination()、DeleteDestination()和 ClearDestinations()方法來完成。例如,下面的語句表示的是讓RTP會話將數據發送到本地主機的6000端口:
unsigned long addr = ntohl(inet_addr("127.0.0.1"));
sess.AddDestination(addr, 6000);
目標地址全部指定之后,接着就可以調用RTPSession類的SendPacket()方法,向所有的目標地址發送流媒體數據。SendPacket()是RTPSession類提供的一個重載函數,它具有下列多種形式:
int SendPacket(void *data,int len)
int SendPacket(void *data,int len,unsigned char pt,bool mark,unsigned long timestampinc)
int SendPacket(void *data,int len,unsigned short hdrextID,void *hdrextdata,int numhdrextwords)
int SendPacket(void *data,int len,unsigned char pt,bool mark,unsigned long timestampinc,unsigned short hdrextID,void *hdrextdata,int numhdrextwords)
SendPacket()最典型的用法是類似於下面的語句,其中第一個參數是要被發送的數據,而第二個參數則指明將要發送數據的長度,再往后依次是RTP負載類型、標識和時戳增量。
sess.SendPacket(buffer,5,0,false,10);
對於同一個RTP會話來講,負載類型、標識和時戳增量通常來講都是相同的,Jrtplib允許將它們設置為會話的默認參數,這是通過調用 RTPSession類的SetDefaultPayloadType()、SetDefaultMark()和 SetDefaultTimeStampIncrement()方法來完成的。為RTP會話設置這些默認參數的好處是可以簡化數據的發送,例如,如果為 RTP會話設置了默認參數:
sess.SetDefaultPayloadType(0);
sess.SetDefaultMark(false);
sess.SetDefaultTimeStampIncrement(10);
之后在進行數據發送時只需指明要發送的數據及其長度就可以了:
sess.SendPacket(buffer,5);
1.4 Jrtplib數據接收
對於流媒體數據的接收端,首先需要調用RTPSession類的PollData()方法來接收發送過來的RTP或者RTCP數據報。由於同一個 RTP會話中允許有多個參與者(源),既可以通過調用RTPSession類的GotoFirstSource()和GotoNextSource() 方法來遍歷所有的源,也可以通過調用RTPSession類的GotoFirstSourceWithData()和 GotoNextSourceWithData()方法來遍歷那些攜帶有數據的源。在從RTP會話中檢測出有效的數據源之后,接下去就可以調用 RTPSession類的GetNextPacket()方法從中抽取RTP數據報,當接收到的RTP數據報處理完之后,一定要記得及時釋放。下面的代碼示范了該如何對接收到的RTP數據報進行處理:
if (sess.GotoFirstSourceWithData()) {
do {
RTPPacket *pack;
pack = sess.GetNextPacket(); // 處理接收到的數據
delete pack;
} while (sess.GotoNextSourceWithData());
}
Jrtplib為RTP數據報定義了三種接收模式,其中每種接收模式都具體規定了哪些到達的RTP數據報將會被接受,而哪些到達的RTP數據報將會被拒絕。通過調用RTPSession類的SetReceiveMode()方法可以設置下列這些接收模式:
a) RECEIVEMODE_ALL 缺省的接收模式,所有到達的RTP數據報都將被接受;
b) RECEIVEMODE_IGNORESOME 除了某些特定的發送者之外,所有到達的RTP數據報都將被接受,而被拒絕的發送者列表可以通過調用AddToIgnoreList()、DeleteFromIgnoreList()和ClearIgnoreList()方法來進行設置;
c) RECEIVEMODE_ACCEPTSOME 除了某些特定的發送者之外,所有到達的RTP數據報都將被拒絕,而被接受的發送者列表可以通過調用AddToAcceptList ()、DeleteFromAcceptList()和ClearAcceptList ()方法來進行設置。
2 音視頻同步
2.1 RTCP控制參數
由於音視頻流作為不同的RTP會話傳送,它們在RTP層無直接關聯。盡管由一個數據源發出的不同的流具有不同的同步源標識(SSRC),為能進行流同步,RTCP要求發送方給接收方傳送一個唯一的標識數據源的規范名(canonical name),應用層藉此關聯音視頻流,以便實現同步。
RTP/ RTCP中有時間戳(相對和絕對)和序列號等信息,可以利用它實現基於時間戳的多媒體流同步。使用相對時間戳和序列號實現流內同步;使用相對和絕對時間戳的對應關系實現流間同步。獲得相對與絕對時間戳的算法如下:
while ((pack = GetNextPacket()) != NULL)
{
if(srcdat->SR_HasInfo() && srcdat->SR_GetRTPTimestamp() != app->mvideortcprtp)
{
app->mvideortcprtp = srcdat->SR_GetRTPTimestamp();
app->mvideortcpntp = srcdat->SR_GetNTPTimestamp().GetMSW();
srcdat->FlushPackets(); }
DeletePacket(pack);
}
2.2 音視頻流間同步實現
發送端在發送音視頻數據時,同時也會發送SR包,這樣可以使接收方能夠正確使音視頻同步播放。具體實現方法是在接收方每次接收數據包后,再遍歷一次數據源,獲取所有源端的SS_RTPTime與SS_NTPTime這兩則數據,通過獲取音頻端與視頻端的數據,可以利用下面的公式進行計算。先描述變量如表2.1所列:
表 2.1 變量描述表
類型
RTP數據
NTP數據
RTP時戳頻率
音頻
Audio_SRRTPTime
Audio_SRNTPTime
Audio_Fre
視頻
Video_SRRTPTime
Video_SRNTPTime
Video_Fre
從SR包中可以讀出音頻與視頻的RTP與NTP數據,而需要計算的是時戳頻率,利用下述公式:
Audio_Fre=(AudioSRRTPtime2- AudioSRRTPtime1)/( AudioSRNTPtime2- AudioSRNTPtime1); (4.3)
Video_Fre=( VideoSRRTPtime2- VideoSRRTPtime1)/( VideoSRNTPtime2- Video SRNTPtime1); (4.4)
然后計算視頻RTP的時間,即:
Video_RTPTime=Video_SRRTPTime+(Audio_SRNTP-Video_SRNTP)×Video_Fre;
(4.5)
最后按式 (4.6)即可計算出Video_TRUERTP時間,將其與從RTP包中讀出的時間進行比較,就可以進行同步播放了。
(Video_TRUERTP-Video_RTPTime)/Video_Fre=(Audio_TRUERTP-Audio_SRRTPTime)/Audio_Fre; (4.6)
下面采用臨時緩沖區的方法來同步音視頻。因為要保證音頻優先正常傳輸,所以本系統以音頻為主軸,視頻為輔軸。以最大延遲時間為緩沖區大小,存放M個音頻幀數據。當接收端獲得M個音頻幀后開始播放音頻,每次獲得視頻幀時,就計算出當前視頻應當播放RTP時間與現有RTP時間進行比對,如若在120ms以內,迅速播放;延遲120ms以上,則扔掉,然后比對下一幀;還在120ms以內,放入視頻緩沖區內進行儲存,如果視頻緩沖區的大小超過了最大臨時緩沖區的數值,依舊開始播放。