背景
之前的文章我們講過如何使用Live555框架進行組播 RTP/UDP播放,以及使用rtspclient完成rtsp播放。
隨着網絡時代的發展,IPv6的實施也是越來越深入,而Live555框架目前暫未實現IPv6支持,我們對其進行IPv6改造。因爲代碼改造較爲碎片化,本文僅提供思路。
Live555 框架:
Live555框架如下:
其中,我們只需要對以下兩部分進行擴展,
- groupSock:用於網絡交互及數據分發,其封裝socket接口,提供加入/退出組播、數據收發等功能。主要需要擴展IPv6/IPv4 Socket兼容。
- liveMedia:流媒體業務核心,主要需要擴URL兼容及RTSP建連的IPv6支持。
.
groupSock修改思路:
第一步:
改造NetAddress,前後變化:
NetAddress是一個用於保存網絡地址的類,其內部定義了兩個數據成員,分別是用於保存地址數據的u_int8_t* fData(
網絡字節序)和用於指示地址長度的unsigned fLength
,這兩個成員描述了網絡地址。它同時也定義了typedef u_int32_t netAddressBits;
4字節來表示網絡地址。
也即是說,整個Live555是以netAddressBits或者NetAddress對象來描述網絡地址的。
修改方法:
摒棄fData,採用socket來描述網絡地址。
socket結構轉換如下(詳細說明):
那麼我們採用struct sockaddr_storage
來描述一個網絡地址,同時爲了方便轉換爲IPv6/IPv4,定義聯合體:
typedef union NetSockaddr {
struct sockaddr_storage fGeneric;
struct sockaddr_in fIn;
struct sockaddr_in6 fIn6;
} NetSockaddr;
以成員NetSockaddr fSockAddr;來描述網絡地址,就能兼容IPv4和IPv6。
因爲以socket來描述地址,所以把Port類和NetAddress類關聯起來,這樣可以方便通過NetAddress查找其對應的端口號。
因此,接下來我們需要修改整個NetAddress的操作符重載及其函數。爲了方便,可以根據需要增加一些函數供外部使用。
第二步:
將groupSock中使用struct sockaddr_in
及netAddressBits
描述的參數及成員,全部替換爲NetAddress。
爲什麼不直接用struct sockaddr_storage
及字節類型呢?因爲NetAddress本身就具備了描述socket及IP地址的能力,這樣的好處是可以更方便的使用NetAddress提供的操作。
因此,需要改造原本groupSock中涉及到這這個兩個類型的所有函數的實現。
第三步:
GroupsockHelper提供groupSock中socket的創建、讀寫、加入/離開組播操作 ,區分IPv4/IPv6流程,修改該類實現setupStreamSocket、setupDatagramSocket、readSocket、writeSocket
修改較爲簡單,兼容修改爲IPv6/v4混編即可。加入/離開組播,要注意setsockopt設置的組播操作略有不同。
總結:
基本上就是將NetAddress的核心成員從fData改爲fSockAddr,其他的均是配合其修改,還有是socket IPv6編程的修改,也沒有太可以說道的。
貼上NetAddress及Port的定義
class NetAddress {
public:
typedef union NetSockaddr {
struct sockaddr_storage fGeneric;
struct sockaddr_in fIn;
struct sockaddr_in6 fIn6;
} NetSockaddr;
public:
NetAddress();
NetAddress(const char * ip, int family = AF_INET);
NetAddress(struct sockaddr_storage * addr, socklen_t len);
NetAddress(u_int8_t const* data/*should be host order bytes*/, unsigned length=4 /* if ipv4: 32 bits */);
NetAddress(NetAddress const& orig);
virtual ~NetAddress();
NetAddress& operator=(NetAddress const& rightSide);
Boolean operator ==( const NetAddress & a ) const;
Boolean operator !=( const NetAddress & a ) const;
Boolean isMulticast() const;
Boolean isLocal() const;
Boolean isUnSpec() const;
int getFamily() const;
const char* getAddress(char * buf, size_t bufSize) const;
unsigned length() const;
u_int8_t const* data() const; // always in network byte order
struct sockaddr_storage makeSockAddr( Port port, socklen_t * len ) const;
const struct sockaddr_storage* getSockAddr() const { return &fSockAddr.fGeneric; }
u_int16_t getPort() const; // in host byte order
void setPort( u_int16_t port ); // in host byte order
private:
void setAddress(const char * ip, int flags, int family);
void clean();
NetSockaddr fSockAddr;
};
typedef u_int16_t portNumBits;
class Port {
public:
Port(portNumBits num = 0/* in host byte order */);
Boolean operator ==( const Port & a ) const {
return a.num() == this->num();
}
Boolean operator !=( const Port & a ) const {
return a.num() != this->num();
}
portNumBits num() const // in network byte order
{ return fPortNum; }
portNumBits port() const // in host byte order
{ return ntohs(fPortNum); }
private:
portNumBits fPortNum; // stored in network byte order
#ifdef IRIX
portNumBits filler; // hack to overcome a bug in IRIX C++ compiler
#endif
};
liveMedia修改思路:
對於liveMedia的修改比較簡單。
首先也一樣,對於各個文件(source、sink、mediasession,rtspClient等),將struct sockaddr_in
及netAddressBits
描述的參數及成員,全部替換爲NetAddress,然後對應修改函數實現即可。
然後對於rtsp,要注意的是SDP信息的解析函數,在c=字段的鏈接描述中,需要支持IPv6。修改如下:
static char* parseCLine(char const* sdpLine) {
char* resultStr = NULL;
char* buffer = strDupSize(sdpLine); // ensures we have enough space
if (sscanf(sdpLine, "c=IN IP4 %[^/\r\n]", buffer) == 1) {
// Later, handle the optional /<ttl> and /<numAddresses> #####
resultStr = strDup(buffer);
}
else if( sscanf(sdpLine, "c=IN IP6 %[^/\r\n]", buffer) == 1) { //mark by wusc get IP6 ip
resultStr = strDup(buffer);
}
delete[] buffer;
return resultStr;
}
最後就是在rtspClient或者是組播拉流程序,需要修改對URL的解析,區分IPv6,IPv4的URL處理。
可以考慮的幾點是:
- 組播地址中,IPv6形式肯定是[ipv6地址]:端口形式,根據有無[]來區分IPv4/IPv6
- Rtsp地址中,可以判斷主機host,如果URL攜帶’.’,那麼可以判定是IPv4,否則爲IPv6。
測試效果:
以RTSP爲例,將改造後的Live555模塊替換之前的rtsp代理app中(文章鏈接)
用VLC搭建IPv6流服務器,配置爲基於UDP載流的,封裝爲RTP的TS流。
抓包看到rtsp交互流程正常,如下:
其SDP如下:
v=0
o=- 16290660091181941766 16290660091181941766 IN IP6 POM20BFBNSWQMND
s=Unnamed
i=N/A
c=IN IP6 ::
t=0 0
a=tool:vlc 3.0.5
a=recvonly
a=type:broadcast
a=charset:UTF-8
a=control:rtsp://[2001:db8::bc83:14f5:ca89:b694]:8554/wait.ts
m=video 0 RTP/AVP 33
b=RR:0
a=rtpmap:33 MP2T/90000
a=control:rtsp://[2001:db8::bc83:14f5:ca89:b694]:8554/wait.ts/trackID=0
繼續看RTP流是否正常,可以看到PLAY命令後,也收到了IPv6 UDP傳輸的TS流:
至此,就可以確認流程正確,看畫面,正常播放,OK。