在Vovida的基礎上實(shí)現自己的SIP協(xié)議棧(五)
在Vovida的基礎上實(shí)現自己的SIP協(xié)議棧(六)
盧政 2003/08/08
3.3 等待對方的呼叫:
上面花了那么長(cháng)的時(shí)間敘述了如何發(fā)起一個(gè)呼叫,我們再來(lái)介紹一下如何接收一個(gè)呼叫:
當用戶(hù)進(jìn)入Idle狀態(tài)以后,如果系統接收到一個(gè)INVITE消息,系統將進(jìn)入Ring狀態(tài),并且進(jìn)入Opring操作中,這個(gè)時(shí)候硬件設備將播放振鈴聲,這個(gè)時(shí)候如果用戶(hù)決定摘機通話(huà),那么offhook事件就會(huì )產(chǎn)生,同時(shí)OpAnswerCall將使狀態(tài)機進(jìn)入InCall狀態(tài),向主叫發(fā)送200響應消息,同樣RTP/RTCP通道打開(kāi),開(kāi)始通話(huà),如果通話(huà)完畢,雙方掛機,那么互相發(fā)送SIP
Bye消息,OpEndCall將使系統重新回到Idle狀態(tài)。
下圖表示了從接收到INVITE消息通話(huà)完畢的各個(gè)狀態(tài)之間程序各種類(lèi)之間的遷移過(guò)程,和主動(dòng)呼叫的情況一樣,如果協(xié)議棧軟件應用于Marshal或者是Redirection
Server的話(huà),那么采用的協(xié)議流程和下面又有一些不一樣了,在后續章節會(huì )對這些做詳細介紹。
在本圖中粗體的部分表示加入MyEntryOperator隊列中的操作符
正體的部分表示加入MyOperator隊列中的操作符
斜體的部分表示加入MyExitOperator隊列中的操作符

(點(diǎn)擊放大)
下面我們來(lái)詳細地介紹每個(gè)操作:
3.3.1 OpRing等待對方的振鈴消息
OpRing:獲取對端向本地發(fā)送的INVITE消息
const Sptr < State >
OpRing::process( const Sptr < SipProxyEvent > event )
{
Sptr < SipEvent > sipEvent;
sipEvent.dynamicCast( event );
if ( sipEvent == 0 )
{
return 0;
}
Sptr < SipMsg > sipMsg = sipEvent->getSipMsg();
assert( sipMsg != 0 );
//接收INVITE消息;
Sptr < InviteMsg > msg;
msg.dynamicCast( sipMsg );
… …
Sptr < UaCallInfo > call;
call.dynamicCast( event->getCallInfo() );
assert( call != 0 );
//在UaCallInfo中存儲當前的INVITE消息;
call->setRingInvite( new InviteMsg( *msg ) );
call->setContactMsg(*msg);
//保存當前的路由消息;
call->setCalleeRoute1List( msg->getrecordrouteList() );
int numContact = msg->getNumContact();
if ( numContact )
{//保存連接
SipContact contact = msg->getContact( numContact - 1 );
Sptr < SipRoute > route = new SipRoute;
route->setUrl( contact.getUrl() );
call->addRoute1( route );
}
… …
Sptr< BaseUrl > baseUrl = msg->getFrom().getUrl();
assert( baseUrl != 0 );
// Assume we have a SIP_URL
Sptr< SipUrl > sipUrl;
sipUrl.dynamicCast( baseUrl );
assert( sipUrl != 0 );
//獲取主叫的Sip URL
Data callingNum = sipUrl->getUserValue();
callingNum += "@";
callingNum += sipUrl->getHost();
signal->dataList.push_back( callingNum.getData(lo) );
//把主叫和被叫的地址(URL)都裝入設備的信號隊列中,為媒體流和鈴聲回放的RTP信道做準備
SipRequestLine reqLine = msg->getRequestLine();
baseUrl = reqLine.getUrl();
assert( baseUrl != 0 );
sipUrl.dynamicCast( baseUrl );
assert( sipUrl != 0 );
string calledNum = sipUrl->getUserValue().getData(lo);
signal->dataList.push_back( calledNum );
UaDevice::getDeviceQueue()->add( signal );
//獲取主叫的SDP
Sptr remoteSdp;
remoteSdp.dynamicCast (msg->getContentData(0));
bool ringbackTone = false;
//創(chuàng )建本地的SDP
SipSdp localSdp;
if ( remoteSdp != 0 )
{
localSdp = *remoteSdp;
Data host = theSystem.gethostAddress();
if(UaConfiguration::instance()->getNATAddress() != "")
{
host = UaConfiguration::instance()->getNATAddress();
}
//設定本地的SDP
setStandardSdp(localSdp, host,UaDevice::instance()->getRtpPort());
}
//獲取本地狀態(tài)是否當前的硬件狀態(tài)支持Call Waiting
HardwareStatusType hdwStatus = UaDevice::instance()->getHardwareStatus();
//檢驗本地是否支持零聲回放(回放的話(huà)要在零聲消息里增加本地的SDP)
StatusMsg statusMsg;
if (UaConfiguration::instance()->getProvideRingback() &&
hdwStatus == HARDWARE_AVAILABLE &&
(remoteSdp != 0) )
{//提供零聲回放回送183狀態(tài)。
ringbackTone = true;
StatusMsg status( *msg, 183 );
status.setContentData( &localSdp );
call->setLocalSdp( new SipSdp( localSdp ) );
statusMsg = status;
}
else
{
// 提供零聲回放則回送180狀態(tài)
StatusMsg status( *msg, 180 );
statusMsg = status;
}
if ( remoteSdp != 0 && UaConfiguration::instance()->getProvideRingback()
)
{ call->setRemoteSdp( new SipSdp( *remoteSdp ) );
call->setLocalSdp( new SipSdp( localSdp ) );
Sptr < SipSdp > localSdp = call->getLocalSdp();
Sptr < SipSdp > remoteSdp = call->getRemoteSdp();
//這里要建立RSVP會(huì )話(huà),準備開(kāi)始預留路徑。
setupRsvp(*localSdp, *remoteSdp);
}
// TODO Call log Show caller information
Sptr < SipCallId > callId
= new SipCallId( sipEvent->getSipCallLeg()->getCallId() );
if ( hdwStatus == HARDWARE_AVAILABLE )
{
// 處理當前的隊列
UaDevice::instance()->setCallId( callId );
}
else if ( hdwStatus == HARDWARE_CALLWAITING_ALLOWED )
{
//把當前的呼叫放在等待隊列中
UaDevice::instance()->addCallWaitingId( callId );
}
else
{
return 0;
}
sipEvent->getSipStack()->sendReply( statusMsg );
if ( ringbackTone )
{//回放零聲(如果需要的話(huà))。
sendRemoteRingback(*remoteSdp);
}
Sptr < UaStateMachine > stateMachine;
stateMachine.dynamicCast( event->getCallInfo()->getFeature() );
assert( stateMachine != 0 );
//進(jìn)入StateRinging狀態(tài)
return stateMachine->findState( "StateRinging" );
}
3.3.2 OpStartRinging開(kāi)始響鈴
OpStartRinging開(kāi)始振鈴,這個(gè)程序和簡(jiǎn)單,主要是在設備處理隊列中加入振鈴消息,并且由設備對振鈴消息進(jìn)行處理。
3.3.3 OpRingingInvite處理又一個(gè)INVITE消息(呼叫等待)
OpRingingInvite是一個(gè)比較有趣的狀態(tài),如果在StateRinging期間有新的INVITE消息過(guò)來(lái),那么就回送一個(gè)180消息給它,讓它處于一個(gè)呼叫等待的狀態(tài)。
3.3.4 OpAnswerCall被叫打開(kāi)媒體通道開(kāi)始通訊
OpAnswerCall const Sptr < State >
該操作的主要目的在于本地接收到主叫發(fā)送過(guò)來(lái)的Invite消息以后,根據主叫的SDP信息,創(chuàng )建本地的SDP并回送200消息,等待主叫發(fā)送ACK消息,正式打開(kāi)媒體通道。
在SDP中最重要的項目莫過(guò)于"m="媒體流指示和"a="會(huì )話(huà)描述,在其之中定義了載荷類(lèi)型,RTP/RTCP端口,編碼方式這幾個(gè)重要參數。
MediaList:代表的是媒體信息指示也就是所有"m="項目的描述列表
MediaAttrib:代表的是會(huì )話(huà)描述的內容也就是所有"a="項目的描述內容
我們以下列的SDP為例子:
SDP Headers
-----------------------------------------------------------------
Header: v=0
Header: o=CiscoSystemsSIP-IPPhone-UserAgent 13045 2886 IN IP4 192.168.6.20
Header: s=SIP Call
Header: c=IN IP4 192.168.6.20
Header: t=0 0
Header: m=audio 30658 RTP/AVP 0 101
Header: a=rtpmap:0 pcmu/8000
Header: a=rtpmap:101 telephone-event/8000
Header: a=fmtp:101 0-11
Header: m=video 30700 RTP/AVP 102
Header: a=rtpmap:31 H261/9000
OpAnswerCall::process( const Sptr < SipProxyEvent > event )
{
Sptr < UaDeviceEvent > deviceEvent;
deviceEvent.dynamicCast( event );
if ( deviceEvent == 0 )
{
return 0;
}
//檢測是否摘機
if ( deviceEvent->type != DeviceEventHookUp &&
deviceEvent->type != DeviceEventFlash )
{
return 0;
}
Sptr < UaCallInfo > call;
call.dynamicCast( event->getCallInfo() );
assert( call != 0 );
//取得引發(fā)振鈴的INVITE消息。
Sptr < InviteMsg > msg = call->getRingInvite();
assert( msg != 0 );
//取出當前的INVITE消息的CallID
SipCallId callId = msg->getCallId();
//如果當前的CllID不是當前正在處理的CallID那么讓當前的Call處于等待當中。
if ( UaDevice::instance()->isMyHardware( callId ) == false )
{
Sptr < SipCallId > callWaitingId =
//檢驗INVITE的CallID是否處于等待隊列中
UaDevice::instance()->getCallWaitingId();
if ( callWaitingId == 0 )
{
return 0;
}
if ( *callWaitingId != callId )
{
return 0;
}
//如果兩次發(fā)送Invite消息是相同的CallID那么第二個(gè)肯定是Re-Invite消息,取消當前
//的Call ID,因為有可能在前面介紹的呼叫等待當中,有可能在發(fā)送新的INVITE消息的時(shí)//候(OpRingingInvite接收到)讓程序陷入當前OpRing-->OpAnswerCall的狀態(tài),把當前的//這樣在當前的這個(gè)操作中把處于等待的Call
ID消除。
UaDevice::instance()->setCallId( callWaitingId );
UaDevice::instance()->removeCallWaitingId( *callWaitingId );
}
// 取得遠端的SDP
Sptr remoteSdp;
remoteSdp.dynamicCast ( msg->getContentData(0) );
call->setRemoteSdp( new SipSdp( *remoteSdp ) );
StatusMsg status( *msg, 200/*OK*/ );
// 根據Cfg文件配置本地的Url到SDP中。
Sptr< SipUrl > myUrl = new SipUrl;
myUrl->setUserValue( UaConfiguration::instance()->getUserName() );
myUrl->setHost( Data( theSystem.gethostAddress() ) );
myUrl->setPort( atoi( UaConfiguration::instance()->getLocalSipPort().c_str()
) );
if(UaConfiguration::instance()->getSipTransport() == "TCP")
{
myUrl->setTransportParam( Data("tcp"));
}
SipContact me;
me.setUrl( myUrl );
status.setNumContact( 0 ); // Clear
status.setContact( me );
//根據遠端回傳的SDP創(chuàng )建本地的SDP
Sptr localSdp;
localSdp.dynamicCast ( status.getContentData(0) );
//本地的SDP設置不為空的情況
if ( localSdp != 0 )
{
//設定本地的RTP端口號
localSdp->setRtpPort( UaDevice::instance()->getRtpPort() );
// 設定RTP包的傳輸速率
int rtpPacketSize = UaConfiguration::instance()->getNetworkRtpRate();
//取得SDP描述符(會(huì )話(huà)符SdpSession)
SdpSession sdpDesc = localSdp->getSdpDescriptor();
list < SdpMedia* > mediaList;
//取得媒體描述列表例如:所有的以"m="打頭的描述
mediaList = sdpDesc.getMediaList();
list < SdpMedia* > ::iterator mediaIterator = mediaList.begin();
//取得所有的媒體列表所有的以"m="打頭的媒體描述
vector < Data > * formatList = (*mediaIterator)->getStringFormatList();
if ( formatList != 0 )
{
formatList->clear();
}
//采用缺省的方式來(lái)建立媒體名和傳送地址,這里當主叫和被叫沒(méi)有公共的媒體格式的時(shí)候,//被叫返回媒體流的"m"行,設置端口為0并且不返回載荷類(lèi)型。
(*mediaIterator)->addFormat( 0 );
// MediaAttributes表示所有"a="和"a=rtpmap:…"的集合
MediaAttributes* mediaAttrib
//取得媒體流的會(huì )話(huà)屬性列表:(所有的"a="的列表)
//a=rtpmap : <載荷類(lèi)型> <算法名稱(chēng)> / <時(shí)鐘采樣頻率> [/<帶入參數>]
mediaAttrib = (*mediaIterator)->getMediaAttributes();
if ( mediaAttrib != 0 )
{ //取得一個(gè)單純的a=<屬性>:<值>(不包括"a=rtpmap")例如:a=recvonly
vector < ValueAttribute* > * valueAttribList = mediaAttrib->getValueAttributes();
vector < ValueAttribute* > ::iterator attribIterator = valueAttribList->begin();
while ( attribIterator != valueAttribList->end() )
{
char* attribName = (*attribIterator)->getAttribute(); //如果遇見(jiàn)a=ptime的情況,就表示要設置時(shí)長(cháng),那么也就是要設定RTP的幀長(cháng)
if ( strcmp( attribName, "ptime" ) == 0 )
{
rtpPacketSize = Data((*attribIterator)->getValue()).convertInt();
break;
}
attribIterator++;
}
mediaAttrib->flushValueAttributes();
mediaAttrib->flushrtpmap();
//以上是根據原段對段遠端的SDP來(lái)獲得相關(guān)的會(huì )話(huà)屬性值,下面是如何將這些會(huì )話(huà)屬性值//加入當前的本地的 SDP的會(huì )話(huà)屬性列表"a="當中
//設定本地的簡(jiǎn)單的"a=<屬性>:<值> "
ValueAttribute* attrib = new ValueAttribute();
attrib->setAttribute( "ptime" );
LocalScopeAllocator lo;
attrib->setValue( Data( rtpPacketSize ).getData(lo) );
//增加a=rtpmap : <載荷類(lèi)型> <算法名稱(chēng)> / <時(shí)鐘采樣頻率> [/<帶入參數>]
//在本地的a=rtpmap:當中
SdpRtpMapAttribute* rtpMapAttrib = new SdpRtpMapAttribute();
rtpMapAttrib->setPayloadType( 0 );
rtpMapAttrib->setEncodingName( "PCMU" );
rtpMapAttrib->setClockRate( 8000 );
mediaAttrib->addValueAttribute( attrib );
mediaAttrib->addmap( rtpMapAttrib );
}
else//如果mediaAttrib為0的情況,也就是"a="項目為空的情況
{
cpLog(LOG_DEBUG, "no mediaAttrib");
mediaAttrib = new MediaAttributes();
assert(mediaAttrib);
(*mediaIterator)->setMediaAttributes(mediaAttrib);
// create the new value attribute object
ValueAttribute* attrib = new ValueAttribute();
// set the attribute and its value
attrib->setAttribute("ptime");
LocalScopeAllocator lo;
//通過(guò)Cfg文件獲取RTP傳輸速率,創(chuàng )建一個(gè)a=ptime:<分組時(shí)間>的對話(huà)屬性。 attrib->setValue( Data( UaConfiguration::instance()->getNetworkRtpRate()
).getData(lo) );
//add the rtpmap attribute for the default codec
SdpRtpMapAttribute* rtpMapAttrib = new SdpRtpMapAttribute();
rtpMapAttrib->setPayloadType(0);
rtpMapAttrib->setEncodingName("PCMU");
rtpMapAttrib->setClockRate(8000);
// 增加新創(chuàng )建的會(huì )話(huà)屬性到本地的SDP當中
mediaAttrib->addValueAttribute(attrib);
mediaAttrib->addmap(rtpMapAttrib);
}
localSdp->setSdpDescriptor(sdpDesc);
//回送OK給主叫端,并且通告本地的SDP
call->setLocalSdp( new SipSdp( *localSdp ) );
deviceEvent->getSipStack()->sendReply( status );
}
else // 根據遠端創(chuàng )建的本地的SDP為0的情況。
{
cpLog(LOG_DEBUG, "localSdp == 0");
// May not have SDP in original INVITE for 3rd party call control
SipSdp sdp;
Data hostAddr = theSystem.gethostAddress();
if(UaConfiguration::instance()->getNATAddress() != "")
{
hostAddr = UaConfiguration::instance()->getNATAddress();
}
int rtpPort = UaDevice::instance()->getRtpPort();
//重新構造一個(gè)本地的SDP發(fā)送給主叫端,該媒體屬性和主叫方的處于一致。
doAnswerStuff(sdp, remoteSdp, hostAddr, rtpPort);
//在狀態(tài)中回送本地的SDP
status.setContentData( &sdp, 0 );
call->setLocalSdp( new SipSdp( sdp ) );
deviceEvent->getSipStack()->sendReply( status );
}
//轉移工作狀態(tài)到StateInCall
Sptr < UaStateMachine > stateMachine;
stateMachine.dynamicCast( event->getCallInfo()->getFeature() );
assert( stateMachine != 0 );
return stateMachine->findState( "StateInCall" );
}
3.3.5 回到StateInCall狀態(tài)
最后程序被叫和主叫所進(jìn)入的狀態(tài)都回到StateInCall狀態(tài),在這個(gè)狀態(tài)里,被叫接收主叫發(fā)送的ACK消息,如果在其中含有新的SDP的話(huà),就按照新的SDP進(jìn)行處理,否則,不就按照UaCallInfo中所包含的SDP的進(jìn)行處理。
4. 如何在改造現有的終端使之能傳遞視頻流。
目前的Vocal平臺是僅僅支持語(yǔ)音傳輸的,還沒(méi)有考慮到視頻部分,不過(guò)從整個(gè)協(xié)議棧的構造來(lái)說(shuō),我嘗試過(guò)把當前的SIP修改成一個(gè)語(yǔ)音/視頻一體的完整的視頻電話(huà)系統(如果想修改成會(huì )議系統的話(huà),我們稍后在Conference
Server這節里面做介紹)只需要把當前的協(xié)議棧部分增加大概1500-2000行左右的代碼(但不包括Codec部分)就可以完成,我們以增加一個(gè)H.261+能力作為我們對這個(gè)問(wèn)題的討論點(diǎn):
4.1一個(gè)H.261+的Codec的基本構造:
有參加過(guò)視頻壓縮和解壓縮算法開(kāi)發(fā)工作的同志應該知道,一般來(lái)說(shuō)一個(gè)視頻通訊的基本類(lèi)包括一個(gè)如下的流程:
以Openh323中實(shí)現的方式為例:(只敘述H.261+編碼部分)

(點(diǎn)擊放大)
4.2 增加視頻能力所需要做的工作
1.設備驅動(dòng)/壓縮/解壓縮部分:
a. 創(chuàng )建一個(gè)H.261的編碼實(shí)例,構造一個(gè)P64的同步壓縮器,您可以從加洲大學(xué)或者是在OpenH323組織的網(wǎng)站上下載該算法的原代碼。
b. 在輸入上綁定一個(gè)標準的攝相頭部分,您可以按照一個(gè)標準輸入設備來(lái)做,也可以使用PWLIB來(lái)作為這個(gè)設備的輸入通道,進(jìn)行綁定,不過(guò),如果使用PWLIB的時(shí)候整體效率會(huì )降低很多。
c. 最后把編碼類(lèi)和攝相頭聯(lián)合構造一個(gè)標準的輸出/輸入類(lèi)(類(lèi)似于SoundCard的實(shí)例一樣),如果這個(gè)標準類(lèi)的調用接口方法可以構造成和標準的聲音設備一樣,也可以構造成在聲音設備之內。
2.協(xié)議部分的改造:
對于SIP和H.323相比較而言,兩者在視頻通訊的概念上有很大的不同,SIP把所有的媒體訊息理解成相同的RTP流,區別只是帶寬不相同;而H.323則有一種專(zhuān)門(mén)的快速方式把兩者區分開(kāi),首先打開(kāi)語(yǔ)音通道傳送音頻,然后打開(kāi)視頻通道傳送視頻信息,兩者用不同的媒體通道傳輸。后者的最大好處在于,如果帶寬不足的話(huà),至少可以傳遞語(yǔ)音信息到對方。當然我們可以在SIP的協(xié)議前提下做適當的修改模仿H.323的運行方式。
a. 媒體流的描述:
一個(gè)媒體信息的具體內容包括:
一個(gè)H.261的視頻流描述例子:
m=video 513000 RTP/UDP 31
a=rtpmap:31 h261/90000
媒體類(lèi)型:Video表示媒體類(lèi)型,513000表示的是RTP端口號,這個(gè)端口號從管理上來(lái)說(shuō)最好和音頻不相同。
傳送協(xié)議:采用RTP/UDP上傳送,因為目前SIP Stack只支持UDP還不支持AVP的格式;
媒體格式:在RTP中定義的靜態(tài)載荷類(lèi)型文檔號為31;
媒體地址和端口:目的地址和RTP的端口號。
這里最主要改造的地方是OpInviteUrl方法,和用戶(hù)端回送OK消息的OpAnswerCall,這里是創(chuàng )建初始的視頻描述的操作,在他們中間需要增加對視頻流的描述,另外在被叫對主叫送來(lái)的INVITE消息中必須要檢測對方的媒體類(lèi)型是否能解析,如果不行的話(huà)還要在OK消息中做相應的拒絕返回。
例如:
主叫INVITE消息的SDP:
Header: v=0
Header: o=- 1528076688 1528076688 IN IP4 192.168.66.1
Header: s=VOVIDA Session
Header: c=IN IP4 192.168.66.1
Header: t=3177769010 0
Header: m=audio 56104 RTP/UDP 0
Header: a=rtpmap:0 PCMU/8000
Header: a=ptime:20
Header: m=video 56110 RTP/UDP 31
Header: a=rtpmap:0 H261/90000
如果被叫不愿意或者無(wú)能力接受視頻流那么被叫回送的SDP如下:
Header: v=0
Header: o=- 1528076688 1528076688 IN IP4 192.168.66.2
Header: s=VOVIDA Session
Header: c=IN IP4 192.168.66.2
Header: t=3177769010 0
Header: m=audio 56114 RTP/AVP 0
Header: a=rtpmap:0 PCMU/8000
Header: a=ptime:20
Header: m=video 0 RTP/UDP 31
b. RTP/RTCP部分的改造:
首先在現有的音頻RTP/RTCP會(huì )話(huà)基礎上增加一個(gè)視頻的RTP/RTCP會(huì )話(huà),從前面介紹的我們知道視頻和音頻一般來(lái)說(shuō)是不在一個(gè)RTP端口的,那么我們?yōu)榱诵麻_(kāi)的視頻RTP端口當然需要捆綁一個(gè)RTPSession(會(huì )話(huà)),在這里我們需要重寫(xiě)視頻設備實(shí)例的ProcessRTP方法,換一句話(huà)來(lái)說(shuō),就是視頻和音頻設備有自己的ProcessRTP方法,分別從不同的端口進(jìn)程讀取相應的媒體數據流,另外相對視頻信號而言,視頻流的RTP幀肯定相對要大一些,不過(guò)為了保證聲音/視頻同步,一般來(lái)說(shuō)兩者的時(shí)長(cháng)還是需要相等(一般是以20ms的數據幀,在這個(gè)時(shí)長(cháng)內聲音/視頻的RTP的頭和內容的比例還是比較均勻的,不過(guò)這樣的話(huà),要使用Jitter的方式,但是實(shí)際上,視頻/音頻的時(shí)間上并不能做到完全的相等,所以在聲/象同步上并不能完全依賴(lài)SSRC,需要在設計時(shí)采用同步緩沖的方式,大家有興趣的話(huà)可以參加一些RSVP工程組中有關(guān)于QoS改善的討論)。
這樣如果分別有自己的ProcessRTP方法,那么前面的說(shuō)道的在視頻流中如果發(fā)生需要保證音頻帶寬而需要放棄視頻帶寬的情況,這樣的情況我們就很好處理了,在SIP協(xié)議中并不保證主叫可以單獨打開(kāi)一個(gè)音頻通道,那么我們只能在RTP/UDP這一層來(lái)完成,可以利用RTCP中的APP分組來(lái)完成這個(gè)通告,比如主/被一端想終止視頻或者音頻通訊,在RTCP通道中發(fā)送一個(gè)自定義的APP分組就可以了。
但是這樣在初始化階段,如果RSVP在路徑上沒(méi)有預留足夠的資源,那么在開(kāi)始的視頻和音頻通訊就可能會(huì )造成阻塞,可能造成用戶(hù)的媒體通訊超時(shí),而不能向H.323那樣可以先通過(guò)快速方式打開(kāi)一個(gè)音頻通道(0號通道),而后再開(kāi)啟視頻通道,所以上述的方法對于會(huì )話(huà)初始階段毫無(wú)用處。
c. 聲象同步:
任何一個(gè)商業(yè)成功的商業(yè)運作的視頻通訊軟件中都需要比較好的去解決聲象同步這
個(gè)重要的問(wèn)題,我們一般建議采用的方式是建立FIFO隊列緩沖,根據RTP包的SSRC重新排列這些分組,不過(guò)如果在操作系統中采用了Direct X或者是直接讀寫(xiě)FrameBuffer技術(shù)的話(huà),那么FIFO也就不能達到您想直接提高圖象處理速度的能力.
3.QoS的提高:
最后說(shuō)一下Qos的提高工作,視頻通訊的處理不但消耗大量的系統資源,也要消耗大量的網(wǎng)絡(luò )資源,當然按照前面所說(shuō)的簡(jiǎn)單的對QoS進(jìn)行處理當然是不行的,需要采取的策略有以下兩種:
a. RSVP上的改善:視頻通訊預留的帶寬要比原來(lái)音頻的要大很多了,而且我們必須考慮到H.261/263協(xié)議中的幀間幀(IntraFrame)的情況,會(huì )產(chǎn)生突發(fā)性的帶寬需求,所以預留帶寬需要比平均帶寬高30%左右,另外,對RESC/PATH消息對必須在通訊中周期性的傳送,確保證實(shí)帶寬(必須考慮消息對所占用的一定帶寬)。
b. RTP信道上的改善:我們通過(guò)RTCP中的SR/RR分組了解到了丟失率,累計分組數,到達時(shí)延抖動(dòng)等等QoS信息,我們可以通過(guò)這些信息對通訊終端的視頻信號進(jìn)行一定的控制,比如在發(fā)現QoS降低時(shí),可以以降低量化度或單位時(shí)間幀數來(lái)降低帶寬負載。
參考文獻:
RFC3261
RFC2548
RFC2205
RFC2212
RAPI -- An RSVP Application Programming Interface Version 5
Practical VOIP Using Vocal(O'REILLY)
IP Phone Based on IP network and Multimedia communication(WOS)
IP Phone in Internent(Oliver David)
IP網(wǎng)絡(luò )電話(huà)技術(shù)(人民郵電出版社)
視頻壓縮與視頻編碼技術(shù)(中國電力出版社)
(完)
作者聯(lián)系方法:lu_zheng@21cn.com
作者供稿 CTI論壇編輯
亚洲精品网站在线观看不卡无广告,国产a不卡片精品免费观看,欧美亚洲一区二区三区在线,国产一区二区三区日韩
台安县|
淮北市|
惠来县|
石泉县|
托克托县|
郁南县|
闵行区|
鄂尔多斯市|
辽阳县|
陇川县|
江西省|
衡阳县|
鸡泽县|
鸡东县|
图们市|
新化县|
昌邑市|
平乐县|
应城市|
元阳县|
长葛市|
秭归县|
青岛市|
临西县|
称多县|
尼勒克县|
辽中县|
奎屯市|
镇雄县|
南汇区|
民勤县|
邛崃市|
平罗县|
芮城县|
古蔺县|
苏尼特右旗|
荣昌县|
施甸县|
汉中市|
申扎县|
康定县|
http://444
http://444
http://444
http://444
http://444
http://444