前段时间遇到一个项目,要求在IPv6的网络环境中实现RTSP流媒体播放,支持点播和直播。然而,RTSP 1.0协议,RFC2326中并未定义如何支持IPv6,因此很多人认为在IPv6网络环境中无法实现RTSP 1.0的流媒体播放,而RTSP 2.0目前还处于草案状态,并未最终通过评审成为标准。
事实上,在IPv6网络环境中,只需要对RTSP 1.0略作修改就可以实现,一些厂商也已经这样做了,比如AXIS。
本文将要介绍的,是如何在开源软件VLC的基础上,修改它的RTSP协议栈使之能够支持IPv6,并能够在IPv6的网络环境中成功连接AXIS的网络摄像机实现视频播放的过程。
1 下载并编译VLC
在www.videolan.org下载VLC的最新版本源码,vlc-1.1.0-rc3.tar.bz2,然后按照网站wiki的说明进行编译。由于是要在win32平台编译,我倾向用MinGW编译环境。
http://wiki.videolan.org/Win32CompileMSYSNew这里给出了VLC编译的详细步骤,严格按照这里给出的步骤操作,除了中间有一次libtool脚本的一个错误,Google上很容易找到解决的方法,之后就没有遇到其他的问题,成功编译出VLC的执行码和所有的plugin。
网上经常有人抱怨下载的VLC源码无法成功编译,遇到各种各样的问题,绝大多数时候,是因为没有严格遵照wiki上的步骤来操作。
VLC编译成功之后,接下来,我们需要聚焦到liblive555_plugin.dll这个插件上来,因为是这个插件实现了VLC的RTSP客户端协议栈。我们除了要修改modules/demux/live555.cpp之外,还需要修改并重新编译live555的库文件。
2 下载并编译Live555
在www.live555.com 下载live555的最新源码包,解压到/home/[user]/live 下,然后运行:
cd live
./genMakefiles mingw
make
编译成功后将生成的libgroupsock.a, libliveMedia.a,libBasicUsageEnvironment.a, libUsageEnvironment.a拷贝到 /win32/lib 路径下,覆盖原来的live555库文件,再把相关的头文件拷贝到 /win32/include 路径下,覆盖原来的live555 头文件。
接下来,需要重新编译VLC,确保这个版本的live555与vlc可以配合工作。
上面这些工作完成后,我们就有了一套可以用来进行IPv6改造的可用的基础代码。
3 Live555的修改要点
3.1 NetAddress.hh
NetAddress.hh中定义了网络地址的基础数据结构,可以看到设计者考虑到了未来向IPv6的扩展,但是做的还不够。设计者原本考虑的是在NetAddress类中放入一个struct sin_addr或者是一个struct sin6_addr,但这样的设计不能够满足IPv6时socket的各种操作,因此,我决定将整个struct sockaddr_in或struct sockaddr_in6都放在NetAddress的fData缓冲区中。
在NetAddress.hh的NetAddress类中增加一个public接口函数getFamily,用于确定这个NetAddress是IPv4地址还是IPv6地址:
int getFamily()
{
struct sockaddr* addr = (structsockaddr*)fData;
return addr->sa_family;
}
3.2 inet.c
inet.c中实现了IP地址从字符串形式与网络地址形式之间的相互转换。IPv4地址的字符串形式是ddd.ddd.ddd.ddd,其中d是一个十进制数,ddd的取值范围是0~255;IPv6地址的字符串形式是xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx,其中x是一个十六进制数,xxxx的取值范围是0~ffff,地址中如果存在连续多个字段为0,可以采用双冒号的形式进行压缩,比如fe80::1表示fe80:0:0:0:0:0:0:1。
our_inet_addr用于将一个字符串形式的IP地址转换为网络地址,要兼容IPv6地址就必须进行改写。
将our_inet_addr函数的实现修改为(其中inet_pton函数可以从linux代码中抠过来):
structsockaddr* our_inet_addr(char* cp)
{
char buf[255];
char* p;
int i;
bzero(buf, 255);
if (cp == NULL)
return NULL;
if (strstr(cp, ":") != NULL) // IPv6 address
{
struct sockaddr_in6 * a6 = (structsockaddr_in6*)malloc(sizeof(struct sockaddr_in6));
memset(a6, 0, sizeof(structsockaddr_in6));
a6->sin6_family = AF_INET6;
if (cp[0] == '[') // If [] exists, strip it off
{
p = strstr(cp, "]");
if (p == NULL)
{
free(a6);
return NULL;
}
memcpy(buf, cp+1, p-cp-1);
}
else
{
strcpy(buf, cp);
}
if (! inet_pton(AF_INET6, buf,&a6->sin6_addr.s6_addr[0]) )
{
free(a6);
return NULL;
}
return (struct sockaddr*)a6;
}
else
{
struct sockaddr_in* a4 = (structsockaddr_in*)malloc(sizeof(struct sockaddr_in));
memset(a4, 0, sizeof(structsockaddr_in));
a4->sin_family = AF_INET;
if (!inet_pton(AF_INET, cp,&a4->sin_addr.s_addr))
{
free(a4);
return NULL;
}
return (struct sockaddr*)a4;
}
}
3.3 groupsockhelper.cpp
函数setupDatagramSocket的定义修改为:
intsetupDatagramSocket(UsageEnvironment& env, Port port, int family=AF_INET);
并在内部在创建socket时将AF_INET修改为动态的family参数。
函数setupStreamSocket的定义修改为:
intsetupStreamSocket(UsageEnvironment& env, Port port, BooleanmakeNonBlocking, int family=AF_INET);
并在函数内部做如下修改:
ü 创建socket时将AF_INET修改为动态的family参数
ü bind所使用的地址,原本由MAKE_SOCKADDR_IN宏来创建,只支持IPv4,改为如下代码来支持IPv6:
struct sockaddr_in6 addr;
int addrLen = 0;
memset(&addr, 0, sizeof(addr));
if (family == AF_INET)
{
((struct sockaddr_in*)&addr)->sin_family= AF_INET;
((structsockaddr_in*)&addr)->sin_port = htons(port.num());
memcpy(&((structsockaddr_in*)&addr)->sin_addr.s_addr), ReceivingInterfaceAddr, 4);
addrLen = sizeof(structsockaddr_in);
}
else if (family == AF_INET6)
{
addr.sin6_family = AF_INET6;
addr.sin6_port =htons(port.num());
addr.sin6_flowinfo = 0;
addr.sin6_scope_id = g_scope_id;
memcpy(&addr.sin6_addr.s_addr,ReceivingInterfaceAddr, 16);
addrLen = sizeof(structsockaddr_in6);
}
if(bind(newSocket, (struct sockaddr*)&addr, addrLen) != 0)
…
函数readSocket的定义修改为:
intreadSocket(UsageEnvironment& env,
int socket, unsigned char* buffer, unsigned bufferSize,
struct sockaddr* fromAddress, int* addrsize)
函数writeSocket的定义修改为:
BooleanwriteSocket(UsageEnvironment& env,
int socket, struct sockaddr* address, Port port,
u_int8_t ttlArg,
unsigned char* buffer, unsigned bufferSize) {
3.4 netaddress.cpp
NetAddressList的构造函数中涉及到对字符串形式的目标地址进行解析并返回一个或多个NetAddress地址。域名解析我们暂不考虑,假定输入的目标地址是IPv4或IPv6形式。
NetAddressList::NetAddressList(charconst* hostname)
: fNumAddresses(0), fAddressArray(NULL) {
struct hostent* host;
// Check first whether "hostname"is an IP address string:
struct sockaddr* addr =our_inet_addr((char*)hostname);
int addrLen = sizeof(struct sockaddr);
if (addr->sa_family == AF_INET)
addrLen = sizeof(structsockaddr_in);
else if (addr->sa_family == AF_INET6)
addrLen = sizeof(structsockaddr_in6);
if (addr != NULL) { // yes it was an IPaddress string
//##### host =gethostbyaddr((char*)&addr, sizeof (netAddressBits), AF_INET);
host = NULL; // don't bother callinggethostbyaddr(); we only want 1 addr
if (host == NULL) {
// For some unknown reason,gethostbyaddr() failed, so just
// return a 1-element list with theaddress we were given:
fNumAddresses = 1;
fAddressArray = newNetAddress*[fNumAddresses];
if (fAddressArray == NULL) return;
fAddressArray[0] = newNetAddress((u_int8_t*)addr, addrLen);
return;
}
} else { // Try resolving"hostname" as a real host name
host =our_gethostbyname((char*)hostname);
if (host == NULL) {
// It was a host name, and we couldn'tresolve it. We're SOL.
return;
}
}
u_int8_t const** const hAddrPtr = (u_int8_tconst**)host->h_addr_list;
if (hAddrPtr != NULL) {
// First, count the number of addresses:
u_int8_t const** hAddrPtr1 = hAddrPtr;
while (*hAddrPtr1 != NULL) {
++fNumAddresses;
++hAddrPtr1;
}
// Next, set up the list:
fAddressArray = newNetAddress*[fNumAddresses];
if (fAddressArray == NULL) return;
for (unsigned i = 0; i <fNumAddresses; ++i) {
fAddressArray[i] = newNetAddress(hAddrPtr[i], host->h_length);
}
}
}
以上这些函数接口修改过之后,在整个live555的代码中做如下检查:
ü 搜索代码中的struct sockaddr_in,全部修改为通用的struct sockaddr,并根据sa_family字段来确定实际的地址类型和地址长度
ü 搜索代码中的’AF_INET’,替换为实际的地址类型
ü 搜索代码中的struct in_addr,修改为NetAddress,根据实际地址类型,实际使用struct sin_addr或者struct sin6_addr
以上修改都做完之后,重新编译live555生成库文件。
4 编译liblive555_plugin.dll
第3节重新编译的live555库文件,拷贝到 /win32/lib 下,并将live555 的相关头文件,拷贝到 /win32/include 下,覆盖原来的头文件。
在 modules/demux/live555.cpp 中任意键入一个空格并存盘,更新其修改时间,然后重新编译liblive555_plugin:
cdvlc/modules/demux
make
cd../..
makepackage-win32-base
5 IPv6测试
使用第4节编译生成的vlc,打开AXIS摄像机的IPv6链接:
rtsp://[fe80::240:8cff:fe94:451e]/axis-media/media.amp
即可看到摄像机的画面在VLC中播放出来。