??xml version="1.0" encoding="utf-8" standalone="yes"?>
OPAL是Open Phone Abstraction LibraryQ是Openh323的下一个版本,它仍然用了Openh323的体pȝ构,q在其基上进行扩展,同时实现了SIP,H.323Q但在音频和视频的编码和传输部分有较大改动。OPAL初衷设计是包含Q何电话通信协议Q所以其底层q行了高度的抽象化,所以也能够很容易的支持MGCP,PSTN和将来会出现的协议。不q由于Openh323的最后一个版本还在开发中Q所以原?月发布的OPAL也被推迟Q现有的OPALq非怸完善QBUG也非常多Q不q相信以Openh323的开发班底,一定能让OPAL十分优秀?
CVS : :pserver:anonymous@cvs.sourceforge.net:/cvsroot/openh323/opal
Language : C++
VxWorks port : Yes
Win32 port : Yes
Linux port : Yes
Supports RFC 3261 : Yes
Supports RFC 2327 : Yes
Supports RFC 3264 : Yes
Supports RFC 3263 : No
Supports RFC 3515 : Yes
Supports RFC 3262 : No
Supports RFC 3311 : No
TCP : Yes
UDP : Yes
SIZE : 8MB
License : MPL
Document : None
Samples : UA,GK
VOCAL是vovida.org开发的SIPpȝQVOCAL应该是目前功能最完善Q用者最多的开源SIP协议栈了.它不只包括了协议栈,q包括了h323与sip转换|关Q对SIP的各UServer的功能支持也非常完善.不过很可惜,不支持windowsq_Q而且自从vovida被CISCO收购以后停止了开发,最后的版本?003q?月的1.5.0?
CVS : :pserver:anonymous@cvs.vovida.org:/cvsroot/vocal
Language : C++
VxWorks port : No
Win32 port : Partial
Linux port : Yes
Supports RFC 3261 : Partial
Supports RFC 2327 : Yes
Supports RFC 3264 :
Supports RFC 3263 :
Supports RFC 3515 : Yes
Supports RFC 3262 :
Supports RFC 3311 :
TCP : Yes
UDP : Yes
SIZE : 6MB
License: Vovida software licencse
Document : Few
Samples : UA,GK,GW
sipX是一个SIPpȝQ由SIPFoundry开发。sipX是从reSIProcate分离出来的,sipX除了包括SIP stack外,q包括了sipXphone,sipXproxy,sipXregistry{等...,由它们构成了完整的SIPpȝQ而且sipxq支持嵌入式pȝQ各个模块可以按需取舍。不q可惜是几乎没有M开发文?
SVN : http://scm.sipfoundry.org/viewsvn/
Language : C++
VxWorks port : Yes
Win32 port : Yes
Linux port : Yes
Supports RFC 3261 : Yes
Supports RFC 2327 : Yes
Supports RFC 3264 : Yes
Supports RFC 3263 : Yes
Supports RFC 3515 : Yes
Supports RFC 3262 : No
Supports RFC 3311 : No
TCP : Yes
UDP : Yes
SIZE : <4 Mb
License : LGPL
Document : None
Samples : UA,GK,GW
ReSIProcate同样也是由SIPFoundry开发,ReSIProcate最开始v源于Vocal,׃Vocal开始只支持rfc3254Qؓ了支持最新的rfc3261,ReSIProcate诞生了,但现在,ReSIProcate已经成ؓ一个独立SIP协议栈了Q它十分E_Qƈ且很多商业程序都在用?
SVN : http://scm.sipfoundry.org/viewsvn/resiprocate/main/sip/
Language : C++
VxWorks port : No
Win32 port : Yes
Linux port : Yes
Supports RFC 3261 : Yes
Supports RFC 2327 : Yes
Supports RFC 3264 : Yes
Supports RFC 3263 : Partial
Supports RFC 3515 : Yes
Supports RFC 3262 : No
Supports RFC 3311 : No
TCP : Yes
UDP : Yes
SIZE : < 2.5 Mb
License : Vovida
Document : Few
Samples : None
oSIP的开发开始于2000q?月,W一个版本在2001q?月发布,到现在已l发展到2.0.9了。它采用ANSI C~写Q而且l构单小巧,所以速度特别快,它ƈ不提供高层的SIP会话控制API,它主要提供一些解析SIP/SDP消息的API和事务处理的状态机QoSIP的作者还开发了ZoSIP的UA lib:exosip和proxy server lib:partysip.
CVS : :ext:anoncvs@savannah.gnu.org:/cvsroot/osip
Language : C
VxWorks port : Yes
Win32 port : Yes
Linux port : Yes
Supports RFC 3261 : Yes
Supports RFC 2327 : Yes
Supports RFC 3264 : Yes
Supports RFC 3263 : Yes
Supports RFC 3515 : No
Supports RFC 3262 : No
Supports RFC 3311 : Yes
TCP : Yes
UDP : Yes
SIZE : 400kb
License : LGPL
Samples : UA,GK
l合上述评测Q可以看?USIP协议栈各有千U,OPAL有发展潜力,VOCAL比较完善QsipX兼容性好QReSIProcate教稳定,oSIPy而快速。所以要Ҏ应用的不同选择恰当的协议栈q行研究开发?/p>
? 发送端的Filter Graph
接收端的实现思\如下:通过一个接收Filter接收发送端发送的数据Q然后再用Divx Decoder FilterҎ收到的数据进行解码。最后用Video Renderer把解码后的数据播攑և来。其Filter Graph如图2所C:
? 接收端的Filter Graph
3.1 数据采集及编码的实现
3.1.1 采集Filter Graph的实?/strong>
采集应用的Filter Graph一般比较复杂,而直接用Filter Graph Manager上的IGraphBuilder接口构徏q种Filter Graph,有时候难度又很大。ؓ此,DirectShow特别提供了一个辅助组件Capture Graph Builder,来简化这UFilter Graph的创建?br> 首先是创建Filter Graph Manager lgQ核心代码如下:
3.1.2 加入采集Filter ? |络发送接收程序流E图 4 ȝ 该系l采用了DirectShow技术实CMPEG-4视频数据的传输,视频数据的传输采用了RTP协议。而且q实Cq端帧率的控Ӟ该系l可以很?便的UL到未?G|络的图像传输系l中。对~解码器q行研究Q采用H.264技术实现编解码Filter是下一步要完成的工作,当然在传输质?QQoSQ方面也要深入进行研I?/p>
参考文?/strong>
3.1.3加入MPEG-4~码器Filter
q里我们采用Divx 提供的开源编码Filter。安装DivX.Pro.v5.1.1后会自动安装Divx的编码器Filter和解码器FilterQ注Q解码器 Filter在接收端要用刎ͼ。在E序中加入Divx的编码器FilterQ实现思想是在Video Compressors目录下枚丑ֈ名称?DivX Pro(tm) 5.1.1 Codec"的Filter后Q把它加入到Filter Graph中即可?br>3.2 数据的发送和接收
3.2.1 数据的发送Filter的实?/strong>
数据的发送要开发一个发送FilterQؓ了编E上的方便,q里采用E序内Filter的Ş式来实现。即用类的Ş式而不是编写一个成一个后~为ax的组?注册后再使用。这里我们定义一个承自CBaseFilter的类CFilterMpeg4Sender。这个类必须实现以下功能[3]Q?br> (1) 在类中定义CFilterMpeg4Sender上的Pin的实例mInputPin?br> (2) 实现l承自CBaseFilter::GetPin,用于q回Filter上各个Pin的对象指针?br> (3) 实现l承自CBaseFilter::GetPin,用于q回Filter上各个Pin的数量?br> 定义一个承自CRenderedInputPin的类CMpeg4InputPinQ用于实现CFilterMpeg4Sender上的输入pinQ发送Filter通过该输入pin接收~码Filter输出的数据,然后按一定的规则发送?br> q个cddC下功能[2]Q?br> (1) 重写ҎEndOfStream?br> (2) 实现IPin::BeginFlush和IPin::EndFlush两个函数?br> (3) 重写ҎCBasePin::CheckMediaTypeq行q接时媒体类型的查?br> (4) 重写ҎCBasePin:: Receive(),接收Sampleq发?br>3.2.2 数据的接收Filter的实?
?据的接收其实是要~写一个Source Filter, q个Source Filter名称为CFilterMpeg4ReceiverQ也l承自CBaseFilter。这跟发送Filter的实现有些类|有一炚w要注意的 是该Filter输出的MediaType的设|?br> Char MediaType[]=//媒体数据cd,通过在发送端把媒体类型写C个文件中而得到然后通过语句QCFilterMpeg4Receiver:: SetupMediaType((char *)MediaType,88)讄输出数据的MediaType?br> CFilterMpeg4Receiver::SetupMediaType再调用CMpeg4OutPin::SetupMediaTypeQ)讄、接收到的媒体数据的格式Q?br>3.2.3 数据的网l传输的实现
数据的发送我们采用开源代码JRTPLIB?】提供的RTP协议栈。最新的JRTPLIB对RFC3550的实现进行了装Q开发h员只要初步了?RTP协议可以开发出高质量的韌频传输程序。用JRTPLIBӞ只需要通过l承RTPSessionc,再重C下几个函数就可以实现视频数据?接收?/p>
在网l带宽比较低的情况下Q如十几KBpsQ,数据丢现象比较严重Q这对于囑փ质量有很大的影响。我们采用拆帧(拆成1400个字节)以后再发送的ҎQ来降低丢率。接收端收到数据后,再把属于同一视频帧的数据再组h?br> |络发送接收程序流E图如图3所C:
对程序流E图的说明如下:
Q?Q发送端拆的算法如下:
然后把属于同一视频帧的数据l好Q发送到解码Filter?br> l过试Q在CDMA1.X|络下)Q采用拆帧方法传输视频数据比直接发送丢包率更低Q传输质量有了很大的提高?br>3.3 数据解码及回攄实现
解码Filter使用的是Divx提供的开源解码器Q在接收Filter的后面接上该解码Filter卛_Q最后接上Renderer Filter可以把接收到的数据回放出来?br>3.4 实现帧率控制功能
通过在采集设备和~码FilterQDivX Pro(tm) 5.1.1 CodecQ之间加入一个率控制Filter来实现率的控制Q该Filter相当于一个视频数计数器Q每接收C帧,q不立即把该帧发l下游的~码 FilterQ而是把计数器的值加1Q当计数器的D到最大值时才把当前收到的发出厅R在接收端发控制帧率命ol采集端可以很方便的实现帧率的远端控 制?br> E序片断如下Q?br>
加了帧率控制Filter的发送端 Filter Graph 如图4所C:
2 Microsoft DirectX C++ SDK Document [EB/OL],2003
3 陆其?DiectShow开发指南[M].北京.清华大学出版C?2004
4 陆其?DiectShow实务_N[M].北京:U学出版C?2004
5 张明?《基于RTP的视频传输控制方法的研究》[D].郑州市:郑州大学, 2004.3
6 Jori Liesenborgs JRTPLIB 3.1.0 [EB/OL]
]]>
![]() ![]() |
![]() ? |络视频和音频交互的Graph?/div> |
static const GUID CLSID_FG729Render = { 0x3556f7d8, 0x5b5, 0x4015, { 0xb9, 0x40, 0x65, 0xb8, 0x8, 0x94, 0xc8, 0xf9 } }; //音频发? static const GUID CLSID_FG729Source = { 0x290bf11a, 0x93b4, 0x4662, { 0xb1, 0xa3, 0xa, 0x53, 0x51, 0xeb, 0xe5, 0x8e } };//音频接收 static const GUID CLSID_FH263Source = { 0xa0431ccf, 0x75db, 0x463e, { 0xb1, 0xcd, 0xe, 0x9d, 0xb6, 0x67, 0xba, 0x72 } };//视频接收 static const GUID CLSID_FH263Render = { 0x787969cf, 0xc1b6, 0x41c5, { 0xba, 0xa8, 0x4e, 0xff, 0xa3, 0xdb, 0xe4, 0x1f } };//视频发?br>//发送和接收韌频数据的filter CComPtr< IBaseFilter > m_pAudioRtpRender ; CComPtr< IBaseFilter > m_pAudioRtpSource ; CComPtr< IBaseFilter > m_pVideoRtpRender ; CComPtr< IBaseFilter > m_pVideoRtpSource ; char szClientA[100]; int iVideoPort = 9937; int iAudioPort = 9938; //构徏视频的graph?q发送数?br>CComPtr< IGraphBuilder > m_pVideoGraphBuilder; //视频囑Ş理? CComPtr< ICaptureGraphBuilder2 > m_pVideoCapGraphBuilder; CComPtr< IBaseFilter > m_pFilterVideoCap; CComPtr< IVideoWindow > m_pVideoWindow; CComPtr< IMediaControl > m_pVideoMediaCtrl ; CComPtr< IBaseFilter > m_pVideoRenderFilter; HRESULT CMyDialog::VideoGraphInitAndSend() { HRESULT hr; hr =m_pVideoGraphBuilder.CoCreateInstance( CLSID_FilterGraph ); if(FAILED(hr)) return hr; hr =m_pVideoCapGraphBuilder.CoCreateInstance( CLSID_CaptureGraphBuilder2); if(FAILED (hr)) return hr; m_pVideoCapGraphBuilder->SetFiltergraph(m_pVideoGraphBuilder); m_pVideoGraphBuilder->QueryInterface(IID_IMediaControl, (void **)&m_pVideoMediaCtrl); m_pVideoGraphBuilder->QueryInterface(IID_IVideoWindow,(void**)&m_pVideoWindow) FindDeviceFilter(&m_pFilterVideoCap,CLSID_VideoInputDeviceCategory); if(m_pFilterVideoCap) m_pVideoGraphBuilder->AddFilter( m_pFilterVideoCap,T2W("VideoCap") ) ; //创徏预览的filter hr = m_pRenderFilterVideo.CoCreateInstance(CLSID_VideoRenderer); if(FAILED(hr)) return hr; m_pVideoGraphBuilder->AddFilter( m_pRenderFilterVideo, L"VideoRenderFilter" ); Connect(m_pFilterVideoCap ,m_pRenderFilterVideo) ; //讄预览的窗?br> CRect rc ; GetClientRect(m_hOwnerWnd, &rc ); int iWidth = rc.right - rc.left ; int iHeight = rc.bottom - rc.top ; int iLeft, iTop; if((iHeight*1.0)/(iWidth*1.0) >= 0.75) { //按宽度算 int tmpiHeight = iWidth*3/4; iTop = (iHeight - tmpiHeight)/2; iHeight = tmpiHeight; iLeft = 0; } else { //按高度算 int tmpiWidth = iHeight*4/3; iLeft = (iWidth - tmpiWidth)/2; iWidth = tmpiWidth; iTop = 0; } m_pVideoWindow->put_Owner( (OAHWND) m_hPreviewWnd ) ; m_pVideoWindow->put_Visible( OATRUE ); m_pVideoWindow->put_WindowStyle( WS_CHILD | WS_CLIPSIBLINGS ) ; //q接到网lƈ发?br> CComPtr< IRtpOption > pRenderOption; CComPtr< IVideoOption > pVideoOption; tagVideoInfo vif(160,120,24); int t=((int)(m_iFrameRate/5)*5)+5; vif.nBitCount=24; vif.nWidth=160; vif.nHeight=120; hr = ::CoCreateInstance(CLSID_FH263Render, NULL, CLSCTX_INPROC, IID_IBaseFilter, (void **)&m_pVideoRtpRender); if(FAILED(hr)) return hr; m_pVideoRtpRender->QueryInterface(IID_IJRTPOption, (void**)&pRenderOption); m_pVideoRtpRender->QueryInterface(IID_IVideoOption,(void**)&pVideoOption); pVideoOption->SetProperty(&vif); pVideoOption->SetSendFrameRate(m_iFrameRate,1);//1 不发送数据,0 实际发送数?br> Connect(m_pFilterVideoCap ,m_pVideoRtpRender) ; //q接Ҏ hr= pRenderOption->Connect(szClientA,iVideoPort,1024); if(FAILED(hr)) return hr; m_pVideoMediaCtrl->Run(); } //视频的接?br>CComPtr< IGraphBuilder > m_pVideoGraphBuilder; //视频囑Ş理? CComPtr< IBaseFilter > m_pFilterVideoCap; CComPtr< IVideoWindow > m_pVideoWindow; CComPtr< IMediaControl > m_pVideoMediaCtrl ; CComPtr< IBaseFilter > m_pVideoRenderFilter; HWND m_hRenderWnd ; HRESULT VideoRecive() { HRESULT hr; hr=CoCreateInstance(CLSID_FilterGraph,NULL,CLSCTX_INPROC, IID_IFilterGraph,(void**)&m_pVideoGraphBuilder); m_pVideoGraphBuilder->QueryInterface(IID_IMediaControl, (void **)&m_pVideoMediaCtrl); m_pVideoGraphBuilder->QueryInterface(IID_IVideoWindow,(void**)&m_pVideoWindow) hr = ::CoCreateInstance(CLSID_FH263Source, NULL, CLSCTX_INPROC, IID_IBaseFilter, (void **)&m_pVideoRtpSource); if(FAILED(hr)) return hr; m_pVideoGraphBuilder->AddFilter(m_pVideoRtpSource, L"My Custom Source"); CComPtr< IRtpOption > m_pRtpOption; CComPtr< IVideoOption > m_pVideoOption; m_pVideoRtpSource->QueryInterface(IID_IJRTPOption, (void **)&m_pRtpOption); m_pVideoRtpSource->QueryInterface(IID_IVideoOption, (void **)&m_pVideoOption); tagVideoInfo vif(160, 120 ,24); m_pVideoOption->SetProperty(&vif); hr= pRenderOption->Connect(szClientA,iVideoPort +1,1024); if(FAILED(hr)) return hr; //创徏预览的filter hr = m_pRenderFilterVideo.CoCreateInstance(CLSID_VideoRenderer); if(FAILED(hr)) return hr; m_pVideoGraphBuilder->AddFilter( m_pRenderFilterVideo, L"VideoRenderFilter" ); Connect(m_pVideoRtpSource ,m_pRenderFilterVideo) ; CRect rc ; GetClientRect(m_hOwnerWnd, &rc ); int iWidth = rc.right - rc.left ; int iHeight = rc.bottom - rc.top ; int iLeft, iTop; if((iHeight*1.0)/(iWidth*1.0) >= 0.75) { //按宽度算 int tmpiHeight = iWidth*3/4; iTop = (iHeight - tmpiHeight)/2; iHeight = tmpiHeight; iLeft = 0; } else { //按高度算 int tmpiWidth = iHeight*4/3; iLeft = (iWidth - tmpiWidth)/2; iWidth = tmpiWidth; iTop = 0; } m_pVideoWindow->put_Owner( (OAHWND) m_hRenderWnd ) ; m_pVideoWindow->put_Visible( OATRUE ); m_pVideoWindow->put_WindowStyle( WS_CHILD | WS_CLIPSIBLINGS ) ; m_pVideoMediaCtrl->Run(); return S_OK; } // HRESULT FindDeviceFilter(IBaseFilter ** ppSrcFilter,GUID deviceGUID) { HRESULT hr; IBaseFilter * pSrc = NULL; CComPtr <IMoniker> pMoniker =NULL; ULONG cFetched; if (!ppSrcFilter) return E_POINTER; // Create the system device enumerator CComPtr <ICreateDevEnum> pDevEnum =NULL; hr = CoCreateInstance (CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC, IID_ICreateDevEnum, (void **) &pDevEnum); if (FAILED(hr)) return hr; // Create an enumerator for the video capture devices CComPtr <IEnumMoniker> pClassEnum = NULL; hr = pDevEnum->CreateClassEnumerator (deviceGUID, &pClassEnum, 0); if (FAILED(hr)) return hr; if (pClassEnum == NULL) return E_FAIL; if (S_OK == (pClassEnum->Next (1, &pMoniker, &cFetched))) { hr = pMoniker->BindToObject(0,0,IID_IBaseFilter, (void**)&pSrc); if (FAILED(hr)) return hr; } else return E_FAIL; *ppSrcFilter = pSrc; return S_OK; } //构徏音频Graph图,q发?br>CComPtr< IGraphBuilder > m_pAudioGraphBuilder; //音频囑Ş理? CComPtr< ICaptureGraphBuilder2 > m_pCapAudioGraphBuilder; CComPtr< IBaseFilter > m_pFilterAudioCap; CComPtr< IMediaControl > m_pAudioMediaCtrl ; HRESULT AudioGraphInit() { HRESULT hr; hr =m_pAudioGraphBuilder.CoCreateInstance( CLSID_FilterGraph ); if(FAILED(hr)) return hr; hr =m_pCapAudioGraphBuilder.CoCreateInstance( CLSID_CaptureGraphBuilder2); if(FAILED (hr)) return hr; m_pAudioGraphBuilder->SetFiltergraph(m_pCapAudioGraphBuilder); m_pAudioGraphBuilder->QueryInterface(IID_IMediaControl, (void **)&m_pAudioMediaCtrl); FindDeviceFilter(&m_pFilterVideoCap,CLSID_AudioInputDeviceCategory); if(m_pFilterAudioCap) m_pAudioGraphBuilder->AddFilter( m_pFilterAudioCap,T2W("AudioCap") ) ; //发送到|络 hr =::CoCreateInstance(CLSID_FG729Render,NULL,CLSCTX_INPROC, IID_IBaseFilter,(void**)&m_pFilterRtpSendAudio) if(FAILED(hr)) return hr; m_pAudioGraphBuilder->AddFilter(m_pAudioRtpRender, L"FilterRtpSendAudio"); Connect(m_pFilterAudioCap,m_pAudioRtpRender); CComPtr< IRtpOption > pOption ; m_pAudioRtpRender->QueryInterface(IID_IJRTPOption,(void**)&pOption) hr =pOption->Connect(szClientA,iAudioPort,1024); if(FAILED(hr)) return hr; m_pAudioMediaCtrl->Run(); return S_OK; } //音频的接? CComPtr< IGraphBuilder > m_pAudioGraphBuilder; //音频囑Ş理? CComPtr< ICaptureGraphBuilder2 > m_pCapAudioGraphBuilder; CComPtr< IBaseFilter > m_pFilterAudioCap; CComPtr< IMediaControl > m_pAudioMediaCtrl ; CComPtr<IBaseFilter> m_pAudioRender; HRESULT AudioRecive() { HRESULT hr; hr =m_pAudioGraphBuilder.CoCreateInstance( CLSID_FilterGraph ); if(FAILED(hr)) return hr; m_pAudioGraphBuilder->QueryInterface(IID_IMediaControl, (void **)&m_pAudioMediaCtrl); hr = m_pAudioRtpSource->CoCreateInstance(CLSID_FG729Source) ; if(FAILED(hr)) return hr; m_pAudioGraphBuilder->AddFilter(m_pAudioRtpSource,L"AudioRtp"); //创徏声卡Renderfilter FindDeviceFilter(&m_pAudioRender,CLSID_AudioRendererCategory); m_pAudioGraphBuilder->AddFilter(m_pAudioRender,L"AudioRender"); CComPtr< IRtpOption > pRtpOption ; m_pAudioRtpSource->QueryInterface(IID_IJRTPOption,(void**)&pRtpOption) hr= pRtpOption->Connect(szClientA,iAudioPort+2,1024); if(FAILED (hr)) return hr; Connect(m_pAudioRtpSource,m_pAudioRender); m_pAudioMediaCtrl->Run(); return S_OK; } |