GB2818集成了sip通訊、RTP封裝及PS流封裝。初涉者不瞭解整體框架,如果每一項去啃讀,每項有幾百頁的標準文檔,啃完估計該吐血了。實際上雖然GB28181裏用了3個項目,但每個單元基本都是固定的,用法比較簡單。
一、關於SIP:GB28181裏只是簡單用了開源的eXosip2和osip2,有興趣可以下載編繹,或直接使用別人編繹好和.h、.lib、.dll,初始化也蠻簡單
1> 初始化
osip_trace_initialize(OSIP_INFO1, NULL);
long result = eXosip_init();
if (result != OSIP_SUCCESS)
return -1;
result = eXosip_listen_addr(IPPROTO_UDP, NULL, m_iMySipPort, AF_INET, 0);
if (result != OSIP_SUCCESS)
return -2;
result = eXosip_listen_addr(IPPROTO_TCP, NULL, m_iMySipPort, AF_INET, 0);
if (result != OSIP_SUCCESS)
return -3;
2>SIP消息處理線程
eXosip_event_t *pSipEvent;
while(m_bActiveSipMsgThread)
{
pSipEvent = eXosip_event_wait(0, 50);
eXosip_lock();
eXosip_default_action(pSipEvent);
eXosip_automatic_refresh();
eXosip_automatic_action();
eXosip_unlock();
if(pSipEvent==NULL)
continue;
switch(pSipEvent->type)
{
case EXOSIP_REGISTRATION_NEW://有註冊進來
break;
case EXOSIP_REGISTRATION_REFRESHED:
break;
case EXOSIP_REGISTRATION_TERMINATED:
break;
case EXOSIP_CALL_NOANSWER:
break;
case EXOSIP_CALL_ANSWERED://請求視頻流回覆成功
break;
case EXOSIP_CALL_CANCELLED:
break;
case EXOSIP_CALL_TIMEOUT:
break;
case EXOSIP_CALL_CLOSED:
break;
case EXOSIP_CALL_MESSAGE_ANSWERED:
break;
case EXOSIP_MESSAGE_NEW://下級平臺保活、設備查詢回覆
break;
case EXOSIP_MESSAGE_ANSWERED://查詢
break;
case EXOSIP_REGISTRATION_FAILURE:
break;
case EXOSIP_REGISTRATION_SUCCESS:
break;
default:
break;
}
eXosip_event_free(pSipEvent);//釋放
}
3>關於sdp
sSDP.Format("v=0\r\n"
"o=%s 0 0 IN IP4 %s\r\n" //owner/creator and session identifier
"s=%s\r\n" //session name
"c=IN IP4 %s\r\n"//connection information
"t=%d %d\r\n" //time the session is active
"m=video %d RTP/AVP 96 98\r\n" //media name and transport address
"a=recvonly\r\n" //zero or more session attribute lines
"a=rtpmap:96 PS/90000\r\n" //
"a=rtpmap:98 H264/90000\r\n"
"y=%d%sd\r\n", //SSRC Y字段:爲十進制整數字符串,表示SSRC值。格式如下:D ddddd dddd(第一位爲歷史或實時媒體,2-5爲SipID的中間(-8),4位StreamID
二、關於RTP
RTP定義本身很複雜,但在GB28181裏很簡單,基本就是固定的12個字節
每一幀視頻或音頻數據,先封裝成ps流,再封裝成RTP包,這個處理是gb28181裏最爲複雜的部分。
RTP頭
struct RTPHeader
{
uint8_t csrccount:4;
uint8_t extension:1;
uint8_t padding:1;
uint8_t version:2;
uint8_t payloadtype:7;
uint8_t marker:1;
uint16_t sequencenumber;
uint32_t timestamp;
uint32_t ssrc;
};
細看比較複雜,其實就是一個12字節的頭,後面是av數據。需要注意以下幾個標識
Marker:如果爲1,表明該幀已經結束,爲0表示是連接的音視頻數據
Sequencenumber:RTP包順序,比如一幀K幀,200K,順序可能是0-199,最後一個包Marker位爲1。
Ssrc:爲流標識,實際可以多個流往一個端口上發,通過此位標識。
Payloadlength:爲該包的長度,如果是前面的包,此值通常爲1024,最後一個長度爲總長除1024的餘數
Payloadoffset:通常爲12,rtp頭信息。
Timestamp:這個值並非每幀的時間戳,但是一個音頻或視頻包此項是相同的。
三、關於PS流封裝
若干個PS包會組成一個AV包(Marker標識一幀結束),以00、00、01在個字節固定開頭,至少需要6個字節,根據第4個字節判斷是音頻幀還是視頻幀
0xBA :I幀(關鍵幀),後面還跟有8字節的ps pack header信息,即ps pack header信息長度爲14字節。
0xBB: // ps system header <18字節>
0xBC:// ps map header <30字節>
0xC0:// 音頻頭
0xE0: //視頻頭 <19字節>
最後根據各字節解析出音視頻包的實際長度。比如一個I幀爲64400,則後面的64400/1024=63個包全是該I幀數據。音頻幀要簡單一些,沒有ps header及map header.
四、注意事項
l 使用多線程接受UDP數據,如果用單線程,需要使用map去檢索。
l 實際UDP包傳輸時如果網絡情況不太好,它的到達順序是不固定的,即有可能先發的包後收到,這個處理起來非常麻煩。如果使用鏈表排序又會降低系統性能。因此需要做好內存管理。