Live555 组播及RTSPClient IPv6改造

背景

之前的文章我们讲过如何使用Live555框架进行组播 RTP/UDP播放,以及使用rtspclient完成rtsp播放。
随着网络时代的发展,IPv6的实施也是越来越深入,而Live555框架目前暂未实现IPv6支持,我们对其进行IPv6改造。因为代码改造较为碎片化,本文仅提供思路。

Live555 框架:

Live555框架如下:
Live555 组播及RTSPClient IPv6改造_第1张图片
其中,我们只需要对以下两部分进行扩展,

  • groupSock:用于网络交互及数据分发,其封装socket接口,提供加入/退出组播、数据收发等功能。主要需要扩展IPv6/IPv4 Socket兼容。
  • liveMedia:流媒体业务核心,主要需要扩URL兼容及RTSP建连的IPv6支持。
    .

groupSock修改思路:

第一步:

改造NetAddress,前后变化:
Live555 组播及RTSPClient IPv6改造_第2张图片
NetAddress是一个用于保存网络地址的类,其内部定义了两个数据成员,分别是用于保存地址数据的u_int8_t* fData(网络字节序)和用于指示地址长度的unsigned fLength,这两个成员描述了网络地址。它同时也定义了typedef u_int32_t netAddressBits; 4字节来表示网络地址。
也即是说,整个Live555是以netAddressBits或者NetAddress对象来描述网络地址的。

修改方法:
摒弃fData,采用socket来描述网络地址。
socket结构转换如下(详细说明):Live555 组播及RTSPClient IPv6改造_第3张图片
那么我们采用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_innetAddressBits描述的参数及成员,全部替换为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修改思路:

对于liveMedia的修改比较简单。
首先也一样,对于各个文件(source、sink、mediasession,rtspClient等),将struct sockaddr_innetAddressBits描述的参数及成员,全部替换为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处理。
可以考虑的几点是:

  1. 组播地址中,IPv6形式肯定是[ipv6地址]:端口形式,根据有无[]来区分IPv4/IPv6
  2. Rtsp地址中,可以判断主机host,如果URL携带’.’,那么可以判定是IPv4,否则为IPv6。

测试效果:

以RTSP为例,将改造后的Live555模块替换之前的rtsp代理app中(文章链接)

用VLC搭建IPv6流服务器,配置为基于UDP载流的,封装为RTP的TS流。
Live555 组播及RTSPClient IPv6改造_第4张图片

抓包看到rtsp交互流程正常,如下:
Live555 组播及RTSPClient IPv6改造_第5张图片
其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流:
Live555 组播及RTSPClient IPv6改造_第6张图片
至此,就可以确认流程正确,看画面,正常播放,OK。

你可能感兴趣的:(live555,音视频,流媒体)