使用 Direct Show 和 Windows Media Format 的流媒体服务器






4.92/5 (31投票s)
一个简单的流媒体服务器,

引言
本文讨论了如何使用 DirectShow 和 Windows Media Format SDK 通过网络传输视频。我正在使用 WMAsfWriter
将媒体样本写入端口。在这里,我试图解释通过网络流式传输实时视频源(如网络摄像头)。
背景
在我最近的文章中,我解释了如何使用 DShow
从流式 URL 捕获图像。之后,我尝试创建自己的流媒体服务器,如 Windows Media 编码器。我还看到了一些关于互联网上的相同查询。最后,我通过 MSDN 和网络上的一些参考资料实现了这一点。我希望我的示例代码能够帮助某人解决他们的问题。
设置您的 Visual Studio 项目
您需要将 Windows Platform SDK 中的头文件和 DShow
基类添加到您的 include
路径。该项目必须与 Strmbase.lib 和 WMVCORE.lib 链接。
#include "atlbase.h" // for using atl support
#include "dshow.h" // DirectShow header
#include "wmsdk.h" // Windows media format SDK
#include "Dshowasf.h" // For asf support
Using the Code
本文将使用以下 DShow
接口
IGraphBuilder *m_pGraph ; //Graph builder
IBaseFilter *m_pVidDeviceFilter; //video device filter
IBaseFilter *m_pAudDeviceFilter; //Audio device filter
IMediaControl *m_pMediaControl; //Media control for running the graph
IMediaEvent *m_pEvent ; //Media Event interface for capture the media events
IBaseFilter *m_pWMASFWritter ; //The WMASFWriter filter is a wrapper for the
//WMWriter object(from WMF) which can multiplux
//the streams into an ASF file and write the ASF
//stream to any IWMWriterSink object.
//It is from wmf SDK.
IFileSinkFilter* m_pFileSinkFilter; //A file sink filter in a video capture filter graph,
//for instance, writes the output of the video
//compression filter to a file.
//If a filter needs the name of an output file,
//it should expose this interface to allow
//an application to set the file name.
IWMWriterAdvanced2 *m_pWriter2 ; //provides the ability to set and retrieve
//named settings for an input. It is from WMF SDK.
IWMWriterNetworkSink *m_pNetSink; //is used to deliver streams to the network.
IServiceProvider *m_pProvider ; //Defines a mechanism for retrieving a service object;
//that is, an object that provides custom support to
//other objects. Here we used to get the
//IWMWriterAdvanced2 interface instance.
我们可以从 CoInitialize
开始,因为这里我们将使用 COM。然后我们需要一个过滤器图来创建捕获图。
HRESULT hr = CoInitialize(NULL);
if(FAILED(hr))
return FALSE;
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **)&m_pGraph);
if(FAILED(hr))
return FALSE;
在这里,我们需要输入设备来获取流媒体的流。以下函数将检索音频和视频设备
GetDevices(CString strDevName,IBaseFilter **pFilter,BOOL bVideo)
{
try
{
CoInitialize(NULL);
ULONG cRetrieved;
IMoniker *pM;
// enumerate all video capture devices
CComPtr<icreatedevenum> pCreateDevEnum;
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum,
NULL, CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum, (void**)&pCreateDevEnum);
if (FAILED(hr))
{
return FALSE;
}
CComPtr<ienummoniker> pEm;
if(bVideo)
{
hr = pCreateDevEnum->CreateClassEnumerator
(CLSID_VideoInputDeviceCategory,
&pEm, 0);
}
else
{
hr = pCreateDevEnum->CreateClassEnumerator
(CLSID_AudioInputDeviceCategory,s
&pEm, 0);
}
if (FAILED(hr))
{
return FALSE;
}
pEm->Reset();
while(hr = pEm->Next(1, &pM, &cRetrieved), hr==S_OK)
{
IPropertyBag *pBag;
hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);
if(SUCCEEDED(hr))
{
VARIANT var;
var.vt = VT_BSTR;
hr = pBag->Read(L"FriendlyName", &var, NULL);
if (hr == NOERROR)
{
TCHAR strdev[2048];
WideCharToMultiByte
(CP_ACP,0,var.bstrVal, -1, strdev,
2048, NULL, NULL);
if(strDevName==strdev)
{
pM->BindToObject(0, 0,
IID_IBaseFilter, (void**)pFilter);
break;
}
SysFreeString(var.bstrVal);
}
pBag->Release();
}
pM->Release();
}
return TRUE;
}
catch(...)
{
return FALSE;
}
}
if(!GetDevices(strVidDevName, &m_pVidDeviceFilter,TRUE))
return FALSE;
if(!GetDevices(strAudDevName, &m_pAudDeviceFilter,FALSE))
return FALSE;
并将这些过滤器添加到图中。
hr=m_pGraph->AddFilter(m_pVidDeviceFilter,L"Vid Capture Filter");
if(FAILED(hr))
return FALSE;
hr=m_pGraph->AddFilter(m_pAudDeviceFilter,L"Aud Capture Filter");
if(FAILED(hr))
return FALSE;
创建 WMAsfWriter
的实例,用于将流写入写入器接收器。
hr = CoCreateInstance(CLSID_WMAsfWriter,NULL,CLSCTX_INPROC_SERVER,
IID_IBaseFilter, (void **) &m_pWMASFWritter);
if(FAILED(hr))
return FALSE;
hr=m_pGraph->AddFilter(m_pWMASFWritter,L"ASF Writter");
if(FAILED(hr))
return FALSE;
然后从 asf 写入器查询文件接收器过滤器,以设置流临时写入的文件名。完成流式传输后,您将获得一个大小为 0 字节的空文件。
hr = m_pWMASFWritter->QueryInterface
( IID_IFileSinkFilter, (void**)&m_pFileSinkFilter );
if(FAILED(hr))
return FALSE;
hr = m_pFileSinkFilter->SetFileName(L"C:\\test.wmv", NULL);
if(FAILED(hr))
return FALSE;
在这里,我们可以使用 IServiceProvider
接口来获取 IWMWriterAdvanced2
实例,以创建写入器网络接收器。
if (FAILED(hr = m_pWMASFWritter->QueryInterface(IID_IServiceProvider,
(void**)&m_pProvider)))
{
AfxMessageBox("Getting service provider failed");
return FALSE;
}
if (FAILED(hr = m_pProvider->QueryService(IID_IWMWriterAdvanced2,
IID_IWMWriterAdvanced2, (void**)&m_pWriter2)))
{
AfxMessageBox ("Query Service failed");
return FALSE;
}
将实时源设置为 true。IWMWriterAdvanced::SetLiveSource
告诉写入器数据的源是否应实时运行。还要删除写入器中的默认接收器。
if (FAILED(hr = m_pWriter2->SetLiveSource(TRUE)))
{
AfxMessageBox ("Setting live source failed");
return FALSE;
}
if (FAILED(hr = m_pWriter2->RemoveSink(0))) //For the time being,
//we are removing the
//default sink...
{
AfxMessageBox (" Remove Sink failed");
return FALSE;
}
m_pProvider->Release(); //Dispose the object after use
通过调用 WMCreateWriterNetworkSink
函数来创建网络接收器对象,该函数返回 IWMWriterNetworkSink
指针。
if (FAILED(hr = WMCreateWriterNetworkSink(&m_pNetSink)))
{
AfxMessageBox ("WMCreateWriterNetworkSink failed");
return FALSE;
}
在网络接收器上调用 IWMWriterNetworkSink::Open
并指定要打开的端口号;例如,8080
。可以选择调用 IWMWriterNetworkSink::GetHostURL
以获取主机的 URL。客户端将从此 URL 访问内容。您还可以调用 IWMWriterNetworkSink::SetMaximumClients
来限制客户端的数量。
CString strPort="";
m_EditPort.GetWindowText(strPort);
DWORD dwPort = 8080;
if(strPort!="")
{
dwPort=atoi(strPort);
}
if (FAILED(hr = m_pNetSink->Open(&dwPort)))
{
AfxMessageBox ("Port opening failed");
return FALSE;
}
WCHAR url[256];
DWORD len = 256;
hr = m_pNetSink->GetHostURL(url, &len);
if(SUCCEEDED(hr))
{
CString strUrl(url);
m_StaticUrl.SetWindowText(strUrl);
}
if (FAILED(m_pWriter2->AddSink(m_pNetSink)))
{
AfxMessageBox ("AddSink failed");
return FALSE;
}
现在我们将把视频设备过滤器和音频设备过滤器连接到 WMASFWriter
。以下函数将这些过滤器连接在一起
hr=ConnectFilters(m_pGraph, m_pVidDeviceFilter,m_pWMASFWritter,"Video Input 01");
if(FAILED(hr))
return FALSE;
hr=ConnectFilters(m_pGraph, m_pAudDeviceFilter,m_pWMASFWritter);
if(FAILED(hr))
return FALSE;
HRESULT ConnectFilters(IGraphBuilder *pGraph,
IBaseFilter *pFirst, IBaseFilter *pSecond)
{
try
{
IPin *pOut = NULL, *pIn = NULL;
HRESULT hr = GetPin(pSecond, PINDIR_INPUT, &pIn);
if (FAILED(hr)) return hr;
// The previous filter may have multiple outputs,
// so try each one!
IEnumPins *pEnum;
pFirst->EnumPins(&pEnum);
while(pEnum->Next(1, &pOut, 0) == S_OK)
{
PIN_DIRECTION PinDirThis;
pOut->QueryDirection(&PinDirThis);
if (PINDIR_OUTPUT == PinDirThis)
{
hr = pGraph->Connect(pOut, pIn);
switch(hr)
{
case S_OK:
break;
case VFW_S_PARTIAL_RENDER:
AfxMessageBox
("VFW_S_PARTIAL_RENDER");
break;
case E_ABORT:
AfxMessageBox("E_ABORT");
break;
case E_POINTER:
AfxMessageBox("E_POINTER");
break;
case VFW_E_CANNOT_CONNECT:
AfxMessageBox
("VFW_E_CANNOT_CONNECT");
break;
case VFW_E_NOT_IN_GRAPH:
AfxMessageBox
("VFW_E_NOT_IN_GRAPH");
break;
}
if(!FAILED(hr))
{
break;
}
}
pOut->Release();
}
pEnum->Release();
pIn->Release();
pOut->Release();
return hr;
}
catch(...)
{
return -1;
}
}
HRESULT ConnectFilters(IGraphBuilder *pGraph,
IBaseFilter *pFirst, IBaseFilter *pSecond,CString strAsfFilterPin)
{
try
{
IPin *pOut = NULL, *pIn = NULL;
HRESULT hr=0;
pIn=GetPinByName(pSecond,
strAsfFilterPin.AllocSysString()); //Get pin by name...
if (FAILED(hr)) return hr;
// The previous filter may have multiple outputs, so try each one!
IEnumPins *pEnum;
pFirst->EnumPins(&pEnum);
while(pEnum->Next(1, &pOut, 0) == S_OK)
{
PIN_DIRECTION PinDirThis;
pOut->QueryDirection(&PinDirThis);
if (PINDIR_OUTPUT == PinDirThis)
{
hr = pGraph->Connect(pOut, pIn);
switch(hr)
{
case S_OK:
//AfxMessageBox
//("Connection Success");
break;
case VFW_S_PARTIAL_RENDER:
AfxMessageBox
("VFW_S_PARTIAL_RENDER");
break;
case E_ABORT:
AfxMessageBox("E_ABORT");
break;
case E_POINTER:
AfxMessageBox("E_POINTER");
break;
case VFW_E_CANNOT_CONNECT:
AfxMessageBox
("VFW_E_CANNOT_CONNECT");
break;
case VFW_E_NOT_IN_GRAPH:
AfxMessageBox
("VFW_E_NOT_IN_GRAPH");
break;
}
if(!FAILED(hr))
{
break;
}
}
pOut->Release();
}
pEnum->Release();
pIn->Release();
pOut->Release();
return hr;
}
catch(...)
{
return -1;
}
}
以下函数将分别按方向和按名称检索引脚。
HRESULT GetPin(IBaseFilter *pFilter,
PIN_DIRECTION PinDir, IPin **ppPin) //get pin by direction
{
try
{
int i=0;
IEnumPins *pEnum;
IPin *pPin;
pFilter->EnumPins(&pEnum);
while(pEnum->Next(1, &pPin, 0) == S_OK)
{
PIN_DIRECTION PinDirThis;
pPin->QueryDirection(&PinDirThis);
if (PinDir == PinDirThis)
{
pEnum->Release();
*ppPin = pPin;
return S_OK;
}
pPin->Release();
}
pEnum->Release();
return E_FAIL;
}
catch(...)
{
return E_FAIL;
}
}
IPin* ::GetPinByName(IBaseFilter *pFilter, LPCOLESTR pinname) //get pin by name
{
try
{
IEnumPins* pEnum;
IPin* pPin;
HRESULT hr = pFilter->EnumPins(&pEnum);
if (FAILED(hr))
return NULL;
while(pEnum->Next(1, &pPin, 0) == S_OK)
{
PIN_INFO pinfo;
pPin->QueryPinInfo(&pinfo);
BOOL found = !_wcsicmp(pinname, pinfo.achName);
if (pinfo.pFilter)
pinfo.pFilter->Release();
if (found)
{
return pPin;
};
pPin->Release();
}
return NULL;
}
catch(...)
{
return NULL;
}
}
在那里我们可以运行图。
hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **)&m_pMediaControl);
if(FAILED(hr))
return FALSE;
hr = m_pGraph->QueryInterface(IID_IMediaEvent, (void **)&m_pEvent);
if(FAILED(hr))
return FALSE;
hr = m_pMediaControl -> Run( );
if(FAILED(hr))
{
AfxMessageBox ("Media Control Run failed");
return FALSE;
}
long evCode=0;
hr=pEvent->WaitForCompletion(INFINITE, &evCode); // Wait till its done.
//please be careful when using INFINITE,
//this may block the entire app indefinitely.
//It will cause the application hang up.
您可以使用以下方法暂停或停止流式传输
hr=m_pMediaControl->Pause();
hr=m_pMediaControl->Stop();
就这样。流媒体方面的一切都完成了。您可以使用 Windows Media Player 作为客户端。只需打开媒体播放器,然后从文件菜单中选择 Openurl
,并将服务器 GUI 中显示的 URL 放在那里并启动。
如果您需要自己的客户端,则可以使用 IWMASFReader
打开此网络位置。您可以使用我的以下文章从此 URL 捕获图像
祝您编码愉快.... :)
参考
历史
- 第一版