Introduction
This article discusses how to stream video over network using DirectShow and Windows Media Format SDK. I am using WMAsfWriter
to write the media sample to the port. Here I tried to explain streaming live video source like Webcam over network.
Background
In my recent article, I have explained how to capture an image from a streaming URL using DShow
. After that, I tried to create my own streaming server like Windows Media encoder. Also I saw some queries about the same on the internet too. At last, I implemented this using some references from MSDN and the web. I hope my sample code will support someone in solving their problem.
Setting Up Your Visual Studio Project
You need to add header files from the Windows Platform SDK and the DShow
base classes to your include
path. The project has to be linked with Strmbase.lib and 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
The following DShow
interfaces will be used in this article:
IGraphBuilder *m_pGraph ; IBaseFilter *m_pVidDeviceFilter; IBaseFilter *m_pAudDeviceFilter; IMediaControl *m_pMediaControl; IMediaEvent *m_pEvent ; IBaseFilter *m_pWMASFWritter ; IFileSinkFilter* m_pFileSinkFilter; IWMWriterAdvanced2 *m_pWriter2 ; IWMWriterNetworkSink *m_pNetSink; IServiceProvider *m_pProvider ;
We can start with the CoInitialize
because here we are going to work with COM. Then we need a filter graph to creating the capture graph.
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;
Here we need the input devices for getting the streams for streaming. The following function will retrieve the audio and video devices:
GetDevices(CString strDevName,IBaseFilter **pFilter,BOOL bVideo)
{
try
{
CoInitialize(NULL);
ULONG cRetrieved;
IMoniker *pM;
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;
and add these filters to graph.
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;
Create an instance of WMAsfWriter
for writing the streams to writer sink.
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;
Then query the file sink filter from asf writer for setting up the file name where the streams temporarily write. Once you completed the streaming, you will get an empty file with size 0 bytes.
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;
Here we can use the IServiceProvider
interface to get the IWMWriterAdvanced2
instance for creating the writer network sink.
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;
}
Setting the live source as true.IWMWriterAdvanced::SetLiveSource
tells the writer whether the source of the data is expected to be running in real time or not. Also remove the default sink in the writer.
if (FAILED(hr = m_pWriter2->SetLiveSource(TRUE)))
{
AfxMessageBox ("Setting live source failed");
return FALSE;
}
if (FAILED(hr = m_pWriter2->RemoveSink(0))) {
AfxMessageBox (" Remove Sink failed");
return FALSE;
}
m_pProvider->Release();
Create the network sink object by calling the WMCreateWriterNetworkSink
function, which returns an IWMWriterNetworkSink
pointer.
if (FAILED(hr = WMCreateWriterNetworkSink(&m_pNetSink)))
{
AfxMessageBox ("WMCreateWriterNetworkSink failed");
return FALSE;
}
Call IWMWriterNetworkSink::Open
on the network sink and specify the port number to open; for example, 8080
. Optionally, call IWMWriterNetworkSink::GetHostURL
to get the URL of the host. Clients will access the content from this URL. You can also call IWMWriterNetworkSink::SetMaximumClients
to restrict the number of clients.
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;
}
Now we will connect the Video device filter and audio device filter to WMASFWriter
. The following functions will connect these filters together:
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;
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());
if (FAILED(hr)) return hr;
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;
}
}
The following functions will retrieve the pin by direction and pin by name respectively.
HRESULT GetPin(IBaseFilter *pFilter,
PIN_DIRECTION PinDir, IPin **ppPin) {
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) {
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;
}
}
There we can run the graph.
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);
You can pause or stop the streaming using the following methods:
hr=m_pMediaControl->Pause();
hr=m_pMediaControl->Stop();
There we go. All are done from the streaming side. You can use the Windows Media player as the client. Just open the media player and select the Openurl
from file menu and put the URL displayed in the server GUI there and start.
If you need your own client, you can use IWMASFReader
to open this network location. You can capture image from this URL by using my following article:
Have a nice coding.... :)
Reference
History