Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Streaming Server using Direct Show and Windows Media Format

0.00/5 (No votes)
13 May 2010 13  
A simple streaming server using Direct Show and Windows Media Format SDK
Server.JPG

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 ; 		//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. 

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

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)))	//For the time being,
						//we are removing the 
						//default sink...
	{
		AfxMessageBox (" Remove Sink failed");
		return FALSE;
	}

	m_pProvider->Release(); 			//Dispose the object after use

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;
		// 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;
	}
}

The following functions will retrieve the pin by direction and pin by name respectively.

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

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); // Wait till its done. 
//please be careful when using INFINITE, 
//this may block the entire app indefinitely.
//It will cause the application hang up.

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

  • First version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here