基于SIP协议的音视频的软件开发Word下载.docx
《基于SIP协议的音视频的软件开发Word下载.docx》由会员分享,可在线阅读,更多相关《基于SIP协议的音视频的软件开发Word下载.docx(12页珍藏版)》请在冰点文库上搜索。
Linux平台下重量级的SIP服务器断。
功能比较丰富,也是很多voip虚拟运营商的系统选择。
不过据说配置比较麻烦,具体没有试过。
5.sipX:
Linux平台下的SIP服务器。
这个好像不能作为客户端再次注册到其他SIP服务器上。
二SIP客户端软件
1.WindowsMessenger5.1:
微软出的SIP客户端,操作方便。
2.YATEClient:
跨平台(Linux,Windows)的开源SIP客户端软件。
安装方便,功能简单。
3.xten:
windows平台下的SIP软电话。
功能齐全,使用方便。
三SIP协议栈
1.osip:
跨平台的开源SIP协议栈。
用c语言实现,体积小。
2.exosip:
对osip进行封装,使其方便SIP客户端软件开发。
同样开源跨平台。
3.sipXtackLib:
由SIPfoundry提供的开源跨平台的SIP协议栈,功能齐全。
C++开发,已经被用于开发数个商业SIP终端。
四RTP栈
1.JRTPLIB:
开源的跨平台rtp栈,用C++语言开发,使用方便。
五抓包测试工具
1.Wireshark:
非常有名的开源跨平台网络抓包工具,以前叫做Ethereal。
内容:
SIP是一个会话协议,很多大企业都在用,通信行业的一个标准(不过从个人角度不喜欢这个协议格式,罗嗦).
其业务逻辑比较,简单地来说如下:
UserAgentServer------------------REGISTER----------->
<
----------401(407)Unauthorized--
----------REG(带上用户口令)----------->
---------------200OK1Bindings---
双方交互几次,注册成功。
因为Sip通信一般采用UDP,所以有个保活的问题,一般每隔两三分钟再向server注册一下。
server也可能每隔一两分钟向客户发Unauthorized,让客户再刷新一下登录。
登录成功后,某个客户端向另一个客户端发起呼叫,通过服务器中转命令。
简单来讲,这个和IM的原理是一样的。
对方同意接收呼叫后,把媒体端口通知给
server及对方。
到了这里,有IM开发经验的人,自然就知道下一步怎么做了:
如果想P2P直连的话,就先穿透NAT打洞,否则就通过Server中转。
很明显,SIP会话和现有的IM类似,但效率或效果上来讲差的很多,比如登录保活,还是同名用户同时登录等等,都处理的不够好。
不过SIP是电信协议,最初是用在VOIP和可视电话上,环境比IM简单地多,所以这个协议足够用了,估计名字中的S也是因为这个原因。
sip呼叫成功,建立连接之后,媒体传输(音视频)是通过RTP协议进行的。
简单地说,采集到声音和视频,先按指定编码方面编码,比如音频编码成g711,视频编码成h263,然后根据RFC相关协议加上包头用UDP向指定发送出去。
对方收到后先解包,再解码,然后播放。
如果想了解SIP的详细工作流程,可以这样:
1找一个外网的sipserver(如果有经验,可以用yate2,或Trixbox等自己搭建)
2安装x-lite(很不错的sip软电话客户端,如果安装eyeBeam更好,带视频)3安装ethereal和WinPcap(抓包工具)
然后,用x-lite拨打其他的客户端或SIP话机,用抓包工具抓出相关的数据包,先看流程,然后再看包结构。
后面附上一个介绍SIP的PPT,写的非常好,可能是台湾方面出品,以前收集的。
是个.rar文件,因为这里只能上传图片,所以改名为.jpg再上传,下载后把.jpg去掉解压就可以了。
PPT写的非常好,用心看,很快就能了解SIP的工作流程。
下一步,就是自己动手实现SIPVOIP系统了。
如果商用的话,server采用Trixbox,也可以仔细研究一下Asterisk。
客户端就用x-lite好了。
做为程序员,第一反应就是怎么样自己动手写一个客户端,甚至服务器。
好在开源产品众多,写一个并不难。
经过几天的调试,发现几个协议栈做的不错:
1SIP协议栈:
aosip+exosip(建立客户端及通信非常简单,质量也好),
breSIProcate(全面,有server端例子,综合调试方便)。
c其他的还用过一个pjsip,不过它与音视频结合成一个库之后,音频质量不好。
但是比较小巧,听说台湾很多嵌入设备采用。
2RTP协议栈:
aLinphone采用的是oRTP,音视频部分采用的是MediaStreamer2bJRtpLib,结合emiplib的音视频处理。
cffmpeg,ffmpeg本来是专门处理音视频编解码的,不过也提供了
rtp,rtsp,最近好象也增加了rtmp协议的支持。
顺便一提,MS2和emiplib底层也采用了ffmpeg。
只要和音视频打交道,并且质量很不错的产品,都离不开它,比如mplayer,ffdshow。
顺便BS一下kmplayer,上了ffmpeg黑名单。
这里面着重提到的是jrtplib,之前误解为它只是按RTP传输数据包,以前写过的几个文章,都是在RTP包之后,自己再封装了一下,当然,做为自己用的音视频聊天程序,这样是没问题的。
但用在SIP及其他VOIP产品上,要考虑互通,就要严格搂RTP协议来执行了。
了解了几个开源的东西,下面自己动手建一个简单的SIP环境:
1对Linux比较熟的人,在CentOS上安装Asterisk,客户端采用Linphone,自己研究吧。
2象我这样只要在Linux下用点g++的,如果想针对VOIP快速学习的话,服务器安装yate2,客户端随便拿哪个都行。
3如果自己想定制sipserver,干脆一步到位,下载reSIProcate,用vc2005编译,一次通过。
运行时提示缺少几个dll,google一下很快都找到了,然后运行repro,做为server先临时用着,反正是学习。
客户端呢,网上流行一个很不错的,名字叫Youtoo,下载,简单编译后可以做为一个语音的客户端使用。
然后,PC上安装几个虚拟机,一个运行server,一个运行x-lite(做为一个参考的标准),主要上运行我们自己写的客户端进行测试。
如果要调试server,就是主机上运行repro,虚拟上分别运行两个x-lite。
环境搭建立好了,下一步就开始调试。
根据这几天的实践,找出了一个最优的配置:
1sipserver采用Trixbox,如果对Linux很熟建议直接用Asterisk.
2客户端如果直接使用,建议ekiga.顺便说一下几个客户端使用的感受:
1linphone:
好象名气不小,不过,最新版3.1.2安装后启动就崩溃。
我安装的是普通的XP-SP3,电脑公司特别版。
一般软件运行没问题。
如果在这个平台上都崩溃,真不知道说什么好。
后来再试3.1.1,这个可以启动,运行能看到视频图像。
不过奇怪的是,与视频电话连接上视频窗口反而隐藏起来。
结束通话,又显示出来。
搞不懂它的视频功能是做什么用的。
另外显示本地视频只支持QCIF。
2eyeBeam:
名气更大,使用起来也不错,这个不错不包含视频功能。
如果启动了视频的话,只显示第一帧图,摄像头怎么转它也不动。
另外,主动连视频电话时,不能启动视频功能。
视频电话呼叫它才能启动。
启动后,"
StartVideo"
点一下又灰住,完全不能用。
3Wengo不错,现在改名叫Quate了,连接摄像头非常快,本地预览也正常。
不过,解码有问题,看到的是一堆绿色图块,不知道是它解不了码,还是弄几个颜色块在那边骗人玩。
4yate好象不支持视频,不过声音倒是不错。
5回头再说ekiga,边续试用了上面几个软件,以为我用的视频电话硬件有问题,但用ekiga连接后,双方的视频都正常。
上面是从视频效果角度出发来评测的,如果不使用视频的话都差不多。
搭建好环境,测试通过,熟悉协议之后,就是自己做一个这样的平台了。
服务器想都不用想,直接用Tixbox,重头写不现实。
至于客户端,一般的程序架构应该如下:
一协议部分:
主要处理sip的注册,呼叫,接收,挂机等功能,所有的协议都差不多,随便选一个就行。
二媒体传输,这部分比较复杂:
1音视频采集
2音视频编码
3音视频编码后组RTP
4RTP/RTCP发送
5RTP/RTCP接收
6从RTP解包还原成编码后的音视频
7音视频解码
8音视频播放
一般如果分配任务,快速做一个客户端,首先想到的就是找一个开源,编译出来再修改。
不过,试了几个,极度痛苦,分别说一下。
1Linphone:
这个产品只能算一般,不过用到的lib非常不错,exosip+osip为sip命令服务,ortp+mediastreamer2为流媒体服务。
不过,编译真是麻烦,别的不说,光
mediastreamer2就用到了ffmpeg,gsm,ortp,srtp,openssl,speex,theora等,稀里湖涂足足花了大半天时间把所有这些都编译好,然后编译ms2.lib时提示几个链接出错。
因为我看到网上几个文章说明是用vc2005轻松编译出来的,我也用的vc2005。
估计用mingw会简单一些。
不过,已经耗了近一天的时间,感觉不爽,放弃。
估计是linphone估计搞的复杂,好让antisip卖钱。
2ekiga
这个需要ptlib,第一感觉这东西很麻烦,不过编译时出奇的顺利(关键是官方提供的资料详细,网友写的文章也详细)。
然后编译opal,也很顺利。
(只是占用机器比较厉害,P42.6的占CPU极严重。
不过,用的机器是联想的超薄机箱那种,不排除官方弄个很烂的CPU冒充。
因为换到另一个P43G,速度快上两三倍)。
其实,编译好opal,基本就可以了,它带了很不错的例子,拨打电话接听都不错。
最后编译ekiga时,需要交叉编译,直接放弃掉,有那时间不如好好研究opal了。
3其他
编译了一下emiplib,这个库写的真不错。
虽然封装的比较深,不过调用时,可以选择比较靠上的类来调用,有点类似ACE。
只是视频格式少了一点。
回头再看上面的一般结构,SIP部分不用操心,随便找个库就能达到目的,关键是媒体传输这部分。
仔细看1-8这些部分,很多我们自己动手就可以做,其实我们并不需要一下完整的全功能的库。
比如,音频采集播放用DirectSound,视频采用播放用DirectShow.编解码用ffmpeg编译出来的libavcodec,传输用jrtplib.这么一看,只有3音视频编码后组RTP和6从RTP解包还原成编码后的音视频这两部分相对陌生,其他的都能找到成熟的代码。
基于这个想法,就不用上述开源产品,直接自己写一个好了。
先做准备工作:
1编译好ffmpeg及所带的libavcodec等几个lib和dll,音视频编解码时需要。
2利用DirectShow做视频采集。
播放就直接用GDI画图好了,简洁。
3声音部分,参考Youtoo这个程序,连SIP都有了,用现成的
exosip,osip,ms2.lib(这个库不支持视频,否则就不用做上面那些苦力了)。
音质相当不错。
4传输就用jrtplib,不过开始为了调试方便,自己写的UDPsocket。
这些准备工作做好,下一步就开始参考RFC进行RTP的组包和解包了。
RTP接收部分比较简单(不用考虑jitterbuffer等),先从这里入手。
其实主要就3步:
1创建一个udp,监听一个端口,比如5200。
2收到RTP包,送到解包程序,继续收第二个。
3收齐一帧后,或保存文件,或解码去播放。
下面详细说一下具体过程:
1创建UDP,非常非常地简单(这里只是简单地模拟RTP接收,虽然能正常工作,但是没有处理RTCP部分,会影响发送端):
lassCUDPSocket:
publicCAsyncSocket{public:
CUDPSocket();
virtual~CUDPSocket();
virtualvoidOnReceive(intnErrorCode);
};
调用者:
CUDPSocketm_udp;
m_udp.Create(...);
这样就可以了。
注意端口,如果指定端口创建不成功,就端口+1或+2重试一下。
重写OnReceive:
voidCUDPSocket:
:
OnReceive(intnErrorCode){charszBuffer[1500];
SOCKADDR_INsockAddr;
memset(&
sockAddr,0,sizeof(sockAddr));
intnSockAddrLen=sizeof(sockAddr);
intnResult=ReceiveFrom(szBuffer,1500,(SOCKADDR*)&
sockAddr,&
nSockAddrLen,0);
if(nResult==SOCKET_ERROR){return;
}
//如果必要可以处理对方IP端口USHORTunPort=ntohs(sockAddr.sin_port);
ULONGulIP=sockAddr.sin_addr.s_addr;
//收到的数据送去解码Decode((BYTE*)szBuffer,nResult);
2收到了数据,开始Decode,一般通过RTP传输的视频主要有h263
(old,1998,2000),h264,mpeg4-es。
mpeg4-es格式最简单,就从它入手。
如果了解RFC3160,直接分析格式写就是了。
如果想偷懒,用现成的,也找的到:
在opal项目下,有个plugins目录,视频中包含了h261,h263,h264,mpeg4等多种解包,解码的源码,稍加改动就可以拿来用。
首先看:
video\common下的rtpframe.h这个文件,这是对RTP包头的数据和操作的封装:
/*****************************************************************************/
/*ThecontentsofthisfilearesubjecttotheMozillaPublicLicense*//*Version1.0(the"
License"
);
youmaynotusethisfileexceptin*//*compliancewiththeLicense.YoumayobtainacopyoftheLicenseat*//*
http:
//www.mozilla.org/MPL/*//**//*SoftwaredistributedundertheLicenseisdistributedonan"
ASIS"
*//*basis,WITHOUTWARRANTYOFANYKIND,eitherexpressorimplied.Seethe*//*Licenseforthespecificlanguagegoverningrightsandlimitationsunder*//*theLicense.*//**//*TheOriginalCodeistheOpenH323Library.*//**//*TheInitialDeveloperoftheOriginalCodeisMatthiasSchneider*//*Copyright(C)2007MatthiasSchneider,AllRightsReserved.*//**//*Contributor(s):
MatthiasSchneider
(ma30002000@yahoo.de)*//**//*Alternatively,thecontentsofthisfilemaybeusedunderthetermsof*//*theGNUGeneralPublicLicenseVersion2orlater(the"
GPL"
),inwhich*//*casetheprovisionsoftheGPLareapplicableinsteadofthoseabove.If*//*youwishtoallowuseofyourversionofthisfileonlyundertheterms*/
/*oftheGPLandnottoallowotherstouseyourversionofthisfileunder*//*theMPL,indicateyourdecisionbydeletingtheprovisionsaboveand*//*replacethemwiththenoticeandotherprovisionsrequiredbytheGPL.*//*Ifyoudonotdeletetheprovisionsabove,arecipientmayuseyour*//*versionofthisfileundereithertheMPLortheGPL.*//**//*TheOriginalCodewaswrittenbyMatthiasSchneider
ma30002000@yahoo.de>
*//*****************************************************************************/#ifndef__RTPFRAME_H__#define__RTPFRAME_H__1#ifdef_MSC_VER#pragmawarning(disable:
4800)//disableperformancewarning#endifclassRTPFrame{public:
RTPFrame(constunsignedchar*frame,intframeLen){_frame=(unsignedchar*)frame;
_frameLen=frameLen;
};
RTPFrame(unsignedchar*frame,intframeLen,unsignedcharpayloadType){_frame=frame;
if(_frameLen>
0)_frame[0]=0x80;
SetPayloadType(payloadType);
}unsignedGetPayloadSize()const{return(_frameLen-GetHeaderSize());
voidSetPayloadSize(intsize){_frameLen=size+GetHeaderSize();
}intGetFrameLen()const{return(_frameLen);
}unsignedchar*GetPayloadPtr()const{return(_frame+GetHeaderSize());
}intGetHeaderSize()const{intsize;
size=12;
if(_frameLen<
12)return0;
size+=(_frame[0]&
0x0f)*4;
if(!
(_frame[0]&
0x10))returnsize;
if((size+4)<
_frameLen)return(size+4+(_frame[size+2]<
8)+_frame[size+3]);
return0;
}boolGetMarker()const{if(_frameLen<
2)returnfalse;
return(_frame[1]&
0x80);
}unsignedGetSequenceNumber()const{if(_frameLen<
4)return0;
return(_frame[2]<
8)+_frame[3];
}voidSetMarker(boolset){if(_frameLen<
2)return;
_frame[1]=_frame[1]&
0x7f;
if(set)_frame[1]=_frame[1]|0x80;
voidSetPayloadType(unsignedchartype){if(_frameLen<
_frame[1]=_frame[1]&
0x80;
_frame[1]=_frame[1]|(type&
0x7f);
}unsignedcharGetPayloadType()const{if(_frameLen<
1)return0xff;
return_frame[1]&
}unsignedlongGetTimestamp()const{if(_frameLen<
8)return0;
return((_frame[4]<
24)+(_frame[5]<
16)+(_frame[6]<
8)+_frame[7]);
}voidSetTimestamp(unsignedlongtimestamp){if(_frameLen<
8)return;
_frame[4]=(uns