用DirectShow实现QQ的音视频聊天功能(ip可视对讲也许用的上)

当下比较流行的即时通信工具,比如MSN,QQ等都实现了视音频的功能,通过视频,音频,我们可以更好的和朋友通过网络进行沟通,本文通过DirectShow技术模拟QQ实现了视频和音频的采集,传输,基本实现了QQ的视音频聊天的功能。

网络视音频系统主要功能就在于视音频的采集,网络传输两个方面,通过Video Capture系列API函数,你就可以轻松的搞定视频捕捉,但是对于视频的网络传输,则要费一番功夫了。 对于视音频数据的传输,只简单地使用数据报套接字传输音视频数据是不可行的,还必须在UDP层上采用RTP(实时传输协议)和RTCP(实时传输控制协议)来改善服务质量。实时传输协议提供具有实时特征的、端到端的数据传输服务。我们在音视频数据前插入包含有载荷标识、序号、时间戳和同步源标识符的RTP包头,然后利用数据报套接字在IP网络上传输RTP包,以此改善连续重放效果和音视频同步。实时传输控制协议RTCP用于RTP的控制,它最基本的功能是利用发送者报告和接收者报告来推断网络的服务质量,若拥塞状况严重,则改用低速率编码标准或降低数据传输比特率,以减少网络负荷,提供较好的Q.S保证。

  Directshow对于音视频的采集提供了很好的接口,利用ICaptureGraphBuilder2接口可以很轻松的建立起视频捕捉的graph图,通过枚举音频设备Filter,也可以很轻松的实现音频的捕捉,有点麻烦的是音视频数据的传输,我们可以自己封装RTP和RTCP的协议,来自己实现一个filter,用来发送和接收音视频数据,当然了Directshow也提供了一组支持使用RTP协议的网络传输多媒体流的Filters。你也完全可以用Directshow提供的RTP系列的filter实现数据的传输。

  下面分析一下这些RTP Filters。

  新定义的Filter包括 RTP Source Filter ,RTP Render Filter,RTP Demux Filter,RTP Receive Playload Handler (RPH) filter,RTP Send Payload (SPH) filter,使用这5个filter构建一个通过RTP协议传输音视频数据的Graph是没有问题的。

  RTP Source filter被用来从一个单独的RTP会话中接收RTP和RTCP包。这个filter提供一个指定发送给其它主机RTCP接收器报告和指定网络地址和端口接口来接收RTP会话的接口。

  RTP Rend filter是用来将数据发到网络上的一个filter,这个filter也提供了和RTP source Filter 类似的接口。

  RTP Demux filter用来多路分离来自 RTP Source filter的RTP 包,这个filter有一个或者多个输出的pin。这个Filter提供了如何控制多路分离和如何分配到特定输出pin的接口。

  RTP RPH Filter 是用来网络过来的RTP包还原成原来的数据格式,主要支持H.261,H.263,Indeo,G.711,G.723和G.729和常见的多种音视频负载类型。

  RTP SPH filter则和RPH filter的功能相对,它的任务是将音视频 压缩filter输出的 数据分解为RTP包,它提供的接口有指定最大生成包大小和pt值。

  下面我们看看如何用这些filter来搭建我们采集和传输的graph图。




  图1和图2展示了DirectShow RTP中定义的filters如何运用。图1是一个采集本地多媒体数据并使用RTP协议通过网络发送的filter graph。它包含一个输出原始视频帧的视频采集filter,紧跟一个压缩帧的编码filter。一旦压缩,这些帧就会被发送到RTP SPH filter,分片打包,生成RTP包,对应的发送到 RTP Render filter,通过网络传输这些包。图2展现了一个filter graph,用来接收包含视频流RTP包,播放视频。这个graph由一个用来接收包的RTP Source filter,一个根据源和负载类型进行分类的RTP Demux filter,一个把RTP包转为压缩视频帧的RTP RPH filter组成。这些filter随后的是用来解压帧的解码filter,一个显示未压缩帧的渲染filter。

  有了RTP filter的帮助我们就可以完成类似qq的功能了,可以实现在网络上进行视频和音频的交互了,下面我给出在网络上两个客户端A和B进行音频和视频交互的Graph图。这里我对图1和图2中的RTP filter进行了自己封装,将编解码filter直接封装到了RTP Source filter 和RTP Render filter中,这样Graph图就显得很简洁,RTP Source filter只是用来接收网络过来的音视频数据,然后将数据传递给客户程序,RTP Render filter则是将采集到的音视频数据发送到网络上的另一个客户端,编解码则的工作则封装到这两个filter之中。


图3 网络视频和音频交互的Graph图


  如果你也想自己封装自己的Source 和Render filter,首先你要选择自己的编解码,视频编解码是选择H261,H263,还是 MEPG4,音频是选择G729还是G711,要首先确定好。选好编解码,封装的工作就简单了。

  不多说了,下面看看我给出的代码吧。

  首先要定义一下用到的四个RTP filter的CLSID。

 

static   const  GUID CLSID_FG729Render  =   0x3556f7d80x5b50x40150xb90x400x650xb80x80x940xc80xf9 } } // 音频发送 
static   const  GUID CLSID_FG729Source  =   0x290bf11a0x93b40x46620xb10xa30xa0x530x510xeb0xe50x8e } } ; // 音频接收
static   const  GUID CLSID_FH263Source  =   0xa0431ccf0x75db0x463e0xb10xcd0xe0x9d0xb60x670xba0x72 } } ; // 视频接收
static   const  GUID CLSID_FH263Render  =   0x787969cf0xc1b60x41c50xba0xa80x4e0xff0xa30xdb0xe40x1f } } ; // 视频发送
// 发送和接收音视频数据的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图,并发送数据
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) ;
 
//设置预览的窗口

 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 ) ;

 
//连接到网络并发送
 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 实际发送数据
 Connect(m_pFilterVideoCap ,m_pVideoRtpRender) ;
 
//连接对方
 hr= pRenderOption->Connect(szClientA,iVideoPort,1024);
 
if(FAILED(hr))
  
return hr;
 m_pVideoMediaCtrl
->Run();
}

// 视频的接收
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(
160120 ,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图,并发送
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;
}

 例子很长转下来慢慢看

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
为易语言编程软件提供了视频聊天功能接口。 主要特性: 1、视频音频的编码解码功能主要移植了ffmpeg、x264和faac等国外开源项目。 2、视频、音频采集分别使用DirectShow、DirectSound。 3、视频、音频播放分别使用GDI、DirectSound。 4、视频编码解码使用H.264算法,低码流高画质。 5、音频编码解码使用AAC算法,低码流高音质。 6、支持枚举视频音频设备,可使用多个摄像头、多个声卡。 7、支持枚举视频设备的分辨率。 8、支持使用自定义分辨率编码视频。 9、支持视频自适应窗口尺寸。 10、支持设置视频帧率。 11、支持设置关键帧间隔。 12、支持可变码流和固定码流。 13、支持视频音频质量设置。 14、支持多种视频运动估计算法,例如钻石搜索、六边形搜索、非对称十字六边形网络搜索、全搜索等。 15、支持设置运动估计搜索范围。 16、支持视频编码线程数设置,多核CPU使用多线程编码更有优势。 17、支持播放缓冲时间设置。 18、支持指定任意窗口播放视频。 19、支持视频音频采集后编码前的回调,因此可在编码前对视频音频进行自定义的相关处理,例如加水印加特效等。 20、支持视频音频解码后播放前的回调,因此可在播放前对视频音频进行自定义的相关处理,例如加水印加特效等。 凌晨孤星 Oct 6 2013 操作系统支持: Windows
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值