之前的文章我们讲过如何使用Live555框架进行组播 RTP/UDP播放,以及使用rtspclient完成rtsp播放。
随着网络时代的发展,IPv6的实施也是越来越深入,而Live555框架目前暂未实现IPv6支持,我们对其进行IPv6改造。因为代码改造较为碎片化,本文仅提供思路。
Live555框架如下:
其中,我们只需要对以下两部分进行扩展,
第一步:
改造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的操作符重载及其函数。为了方便,可以根据需要增加一些函数供外部使用。
贴上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
};
第二步:
将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设置的组播操作略有不同。
如下:
Boolean socketJoinGroup(UsageEnvironment& env, int socket,
NetAddress groupAddress){
if (!groupAddress.isMulticast()) return True; // ignore this case
if( groupAddress.getFamily() == AF_INET ) {
struct ip_mreq imr;
struct sockaddr_in * in = (struct sockaddr_in *)groupAddress.getSockAddr();
imr.imr_multiaddr.s_addr = in->sin_addr.s_addr;
in = (struct sockaddr_in *)ReceivingInterfaceAddr.getSockAddr();
imr.imr_interface.s_addr = in->sin_addr.s_addr;
ALOGD("IPV4 socketJoinGroup \n");
if (setsockopt(socket, IPPROTO_IP, IP_ADD_MEMBERSHIP,
(const char*)&imr, sizeof (struct ip_mreq)) < 0) {
socketErr(env, "setsockopt(IP_ADD_MEMBERSHIP) error: ");
return False;
}
} else if( groupAddress.getFamily() == AF_INET6 ) {
struct ipv6_mreq imr;
struct sockaddr_in6 * in6 = (struct sockaddr_in6 *)groupAddress.getSockAddr();
memcpy(&imr.ipv6mr_multiaddr, &(in6->sin6_addr), sizeof(struct in6_addr));
imr.ipv6mr_interface= 0;
ALOGD("IPV6 socketJoinGroup \n");
if (setsockopt(socket, IPPROTO_IPV6, IPV6_JOIN_GROUP,
(const char*)&imr, sizeof (struct ipv6_mreq)) < 0) {
socketErr(env, "setsockopt(IPV6_JOIN_GROUP ) error: ");
return False;
}
} else {
ALOGD("socketJoinGroup never come here, somethig err \n");
return False;
}
ALOGD(" socketJoinGroup success \n");
return True;
}
Boolean socketLeaveGroup(UsageEnvironment&, int socket,
NetAddress groupAddress) {
if (!groupAddress.isMulticast()) return True; // ignore this case
if( groupAddress.getFamily() == AF_INET ) {
struct ip_mreq imr;
struct sockaddr_in * in = (struct sockaddr_in *)groupAddress.getSockAddr();
imr.imr_multiaddr.s_addr = in->sin_addr.s_addr;
in = (struct sockaddr_in *)ReceivingInterfaceAddr.getSockAddr();
imr.imr_interface.s_addr = in->sin_addr.s_addr;
ALOGD("IPV4 socketLeaveGroup enter\n");
if (setsockopt(socket, IPPROTO_IP, IP_DROP_MEMBERSHIP,
(const char*)&imr, sizeof (struct ip_mreq)) < 0) {
return False;
}
} else if( groupAddress.getFamily() == AF_INET6 ) {
struct ipv6_mreq imr;
struct sockaddr_in6 * in6 = (struct sockaddr_in6 *)groupAddress.getSockAddr();
memcpy(&imr.ipv6mr_multiaddr, &(in6->sin6_addr), sizeof(struct in6_addr));
imr.ipv6mr_interface= 0;
ALOGD("IPV6 socketLeaveGroup enter\n");
if (setsockopt(socket, IPPROTO_IPV6, IPV6_LEAVE_GROUP,
(const char*)&imr, sizeof (struct ipv6_mreq)) < 0) {
return False;
}
} else {
ALOGD("socketLeaveGroup never come here, somethig err \n");
return False;
}
return True;
}
总结:
基本上就是将NetAddress的核心成员从fData改为fSockAddr,其他的均是配合其修改,还有是socket IPv6编程的修改,也没有太可以说道的。
对于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 / and / #####
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处理。
可以考虑的几点是:
以RTSP为例,将改造后的Live555模块替换之前的rtsp代理app中(文章链接)
用VLC搭建IPv6流服务器,配置为基于UDP载流的,封装为RTP的TS流。
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。