A DirectShow Tutorial

Enumerating filters and devices

Filters are registered COM objects made available by the operating system to your applications. Depending on the software applications installed on your machine, different sets of filters might be available. These files are classified by category. There is, for example, a category identified by CLSID_VideoCompressorCategory and that includes all the compression filters available. When you wish to use a filter of a given category, you must enumerate the filters available and select one of these.

To enumerate the filters, the first step consists in the creation of a system device enumerator:

    
ICreateDevEnum *pSysDevEnum; 
CoCreateInstance(CLSID_SystemDeviceEnum, NULL, 
                 CLSCTX_INPROC_SERVER,
                 IID_ICreateDevEnum, 
                 (void **)&pSysDevEnum);
Then an enumerator for a given category is obtained as follows:
    
IEnumMoniker *pEnumCat = NULL;
pSysDevEnum->CreateClassEnumerator(
                  CLSIDcategory, &pEnumCat, 0);
A simple loop is then required to obtain each filter of the category:
    
IMoniker *pMoniker;
ULONG cFetched;
while(pEnumCat->Next(
                 1,         // number of elements requested
                 &pMoniker, // pointer to the moniker
                 &cFetched) // number of elements returned
                    == S_OK)
These ones are identified by the IMoniker interface, an interface used to uniquely identify a COM object. A moniker is similar to a path in a file system and it can be used to obtain information about a given filter:
    
IPropertyBag *pPropBag;
pMoniker->BindToStorage(0, 0, IID_IPropertyBag, 
                               (void **)&pPropBag);
Properties of a filter are obtained using the IPropertyBag interface. This generic interface is used to read and write properties using text. Moniker can also be used to create a filter:
    
IBaseFilter* baseFilter;
pMoniker->BindToObject(NULL, NULL, 
             IID_IBaseFilter, (void**)&baseFilter);
This later approach must be used to create an enumerated filter instead of using the CoCreateInstance function. The function presented below can be used to obtain the available filters of a category. It returns the friendly names and the CLSID identifier of each filter. Either can be used after to create a given filter.
    
void enumFilters(REFCLSID CLSIDcategory, 
                 std::vector<CString>& names, 
                 std::vector<CLSID>& clsidFilters) {

  // Create the System Device Enumerator.
  HRESULT hr;
  ICreateDevEnum *pSysDevEnum = NULL;
  hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, 
                        CLSCTX_INPROC_SERVER,
                        IID_ICreateDevEnum, 
                        (void **)&pSysDevEnum);

  // Obtain a class enumerator for the specified category.
  IEnumMoniker *pEnumCat = NULL;
  hr = pSysDevEnum->CreateClassEnumerator(CLSIDcategory,
                                          &pEnumCat, 0);

  if (hr == S_OK)  {

    // Enumerate the monikers.
    IMoniker *pMoniker;
    ULONG cFetched;
    while(pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK)
    {
	 IPropertyBag *pPropBag;
	 pMoniker->BindToStorage(0, 0, IID_IPropertyBag, 
                                      (void **)&pPropBag);

      // To retrieve the friendly name of the filter
  	 VARIANT varName;
	 VariantInit(&varName);
	 hr = pPropBag->Read(L"FriendlyName", &varName, 0);
	 if (SUCCEEDED(hr))
	 {
	   CString str(varName.bstrVal);
        names.push_back(str);
	   SysFreeString(varName.bstrVal);
	 }

 	 VariantClear(&varName);

      VARIANT varFilterClsid;
      varFilterClsid.vt = VT_BSTR;

      // Read CLSID string from property bag
      hr = pPropBag->Read(L"CLSID", &varFilterClsid, 0);
      if(SUCCEEDED(hr))
	 {
        CLSID clsidFilter;

        // Save filter CLSID 
        if(CLSIDFromString(varFilterClsid.bstrVal,
                               &clsidFilter) == S_OK)
	   {
           clsidFilters.push_back(clsidFilter);
	   }

        SysFreeString(varFilterClsid.bstrVal);
      }
    
  	 // Clean up.
	 pPropBag->Release();
	 pMoniker->Release();
    }

    pEnumCat->Release();
  }

  pSysDevEnum->Release();
}
This function is used to select a compression filter to be used in our sequence processing application. The list of compression filters is displayed in a list box, obtained after the output sequence is selected:
    
void CCvisionDlg::OnSave() 
{
  // Select output file

  ...

  // Obtain and display compressors

  std::vector<CString> fname;
  std::vector<CLSID> fclsid;
			
  enumFilters(CLSID_VideoCompressorCategory, 
              fname, fclsid);

  m_list.ResetContent();
  for (int i=0; i<fname.size(); i++)
    m_list.AddString(fname[i]);
}
The compression filter is selected by clicking on the corresponding item before pushing the process button. The sequence will then be saved, compressed according to the default control parameters of the chosen compressor. What if you are not satisfied with the resulting compression rate? You can obviously try to select another compression filter; however, it is also possible to use different control parameter values for the chosen filter. This can be done through a special interface called IAMVideoCompression. This interface is normally supported by the output pin of a compression filter. You can obtain the interface by calling the QueryInterface method of the pin:
    
IAMVideoCompression *pCompress;
pPin->QueryInterface(IID_IAMVideoCompression,
 						(void**)&pCompress);
Once obtained, the interface can be used to set the compression properties, namely: the key frame rate (a long integer), the number of predicted frames per key frame (also a long integer), and the relative compression quality (a double expressing a percentage between 0.0 and 1.0). It is then easy to set these values using the appropriate methods.
    
long keyFrames, pFrames; 
double quality;
hr = pCompress->put_KeyFrameRate(keyFrames);
hr = pCompress->put_PFramesPerKeyFrame(pFrames);
hr = pCompress->put_Quality(quality);

Check point #4: source code of the above example.

The same strategy can be used to select a video capture device (e.g. a USB camera). The only difference is that these devices obviously do not have input pins. However, they normally have two output pins (one for capture and one for preview). The basic steps to build a camera-based video processing filter graph are as follows. First add the video capture device through enumeration:

    
CString cameraName= ?;
IPin* pSourceOut[2];
pSourceOut[0]= pSourceOut[1]= NULL;
addFilterByEnum(CLSID_VideoInputDeviceCategory,
                  cameraName,pGraph,pSourceOut,2);
Second, add the ProxyTrans filter:
    
addFilter(CLSID_ProxyTransform, L"ProxyTrans", 
                            pGraph, pSourceOut);
Then you should add a renderer to the preview pin:
    
addRenderer(L"Renderer(1)", pGraph, pSourceOut+1);
And finally, you add the required filters to save the resulting sequence to a file:
    
addFilter(CLSID_AviDest, L"AVImux", pGraph, pSourceOut);
addFileWriter(ofilename, pGraph, pSourceOut);
The complete application that includes the camera selection is given here.

Now to be able to change the camera settings (such as resolution or frame rate), you must access the facilities offered by the driver of the camera. The easiest way to do it is to use the old VideoForWindows technology (an ancestor of DirectShow). If the camera you use has a driver compatible with this technology, then it is possible to obtain dialog boxes to control the camera settings. This is done through the IAMVfwCaptureDialogs interface of the camera filter. The first thing to do is then to check if the camera supports this filter and if yes, to check what dialogs are available. The three standard dialogs are designated by an enumerated type: VfwCaptureDialog_Source, VfwCaptureDialog_Format, VfwCaptureDialog_Display. The procedure to obtain one of these dialogs is quite straightforward:
    
IAMVfwCaptureDialogs *pVfw = 0;

// pCap is a pointer to the camera base filter
if (SUCCEEDED(pCap->QueryInterface(
IID_IAMVfwCaptureDialogs, (void**)&pVfw))) {

// Check if the device supports this dialog box.
	if (S_OK == pVfw->HasDialog(VfwCaptureDialog_Format)){

		// Show the dialog box.
		pVfw->ShowDialog(VfwCaptureDialog_Format,
 			hwndParent); // parent window
	}
}
A dialog like the following should then appear:

Check point #5: source code of the above example.

Top of the page

 

(c) Robert Laganiere 2011