Enumerate and Access Hardware Devices in DirectShow Applications


This article explains and demonstrates how to initialize and access system hardware devices by using interfaces and classes provided by Microsoft® DirectShow. Developers need this functionality to support many types of hardware in their applications. Typically, DirectShow applications use the following types of hardware.

Because developers support these devices in a similar manner (and for the sake of brevity), they will be referred to as AV devices for the remainder of this article; they will be distinguished only if a topic applies to a specific type of device.

Three interfaces apply to hardware device support: ICreateDevEnum (documented in the DirectShow SDK), and IPropertyBag and IPersistPropertyBag (both Microsoft Win32® interfaces). These interfaces handle hardware device enumeration and the loading and storage of AV device properties.

Contents of this article:

Application developers who want to control hardware devices should be familiar with the COM-based concepts of monikers, enumerators, and the initialization and creation of DirectShow objects.

How to Enumerate Hardware Devices

Microsoft provides audio and video capture and playback functionality through interfaces, classes, and samples included in the DirectShow SDK. Because the File Source filters and the filter graph manager handle the internal work of passing information from component to component, adding capture capabilities to an application requires a relatively small amount of additional code. The required additional code enumerates the system's hardware devices and compiles a list of the devices that can perform a specific task (a list of all video capture cards, for example). You can use the same enumeration process for any hardware device, past or present; DirectShow automatically instantiates filters for both Win32 and Video for Windows devices.

To work with AV devices, you must first detect what devices exist on the current system. The ICreateDevEnum interface, which creates enumerators for any specified type of object, provides the functionality you need to detect and set up the hardware. Accessing a specific device is a three-step process, detailed by the following instructions and code fragments.

  1. Create a system hardware device enumerator.

    First, set aside a pointer for the enumerator, and then create it by using the CoCreateInstance function; CLSID_SystemDeviceEnum is the type of object you want to create (a system hardware device enumerator, in this case) and IID_ICreateDevEnum is its interface GUID.

    
        ICreateDevEnum  *pCreateDevEnum ;
        CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
    			  IID_ICreateDevEnum, (void**)&pCreateDevEnum) ;
    
  2. Create an enumerator for a specific type of hardware device (such as a video capture card).

    Declare an IEnumMoniker interface pointer and pass it to the ICreateDevEnum::CreateClassEnumerator method, called on the system device enumerator. You can then use the IEnumMoniker interface pointer to access the newly created enumerator.

    
        IEnumMoniker *pEnumMon ;
        pCreateDevEnum->CreateClassEnumerator(
    				[specify device GUID here]
    				&pEnumMon, 0);
    
  3. Enumerate the list itself until you locate the desired device.

    If the previous call to CreateClassEnumerator succeeded, you can call the IEnumMoniker::Next method to step through the list of devices. To retrieve the device itself, call the IMoniker::BindToObject method on an enumerated device. BindToObject creates the filter associated with the selected device and loads the filter's properties (CLSID, FriendlyName, and DevicePath) from the registry. Don't be confused by the (1 == cFetched) portion of the if condition; the Next method will set it to the number of returned objects (1, if successful) before testing the statement's validity.

    
        ULONG cFetched = 0;
        IMoniker *pMon ;
    
        if (S_OK == (pEnumMon->Next(1, &pMon, &cFetched))  &&  (1 == cFetched))
        {
            pMon->BindToObject(0, 0, IID_IBaseFilter, (void **)&[desired interface here]) ;
    

    Now that you have the IMoniker pointer, you can add the device's filter to the filter graph. Once you've added the filter, you don't need the IMoniker pointer, device enumerator, or system device enumerator.

    
    	    pGraph->AddFilter([desired interface here], L"[filter name here]") ;
    	    pMon->Release() ;  // Release moniker
    	}
    	pEnumMon->Release() ;	// Release the class enumerator
        }
        pCreateDevEnum->Release();
    

Device Enumeration in the AMCap Sample

The DirectShow SDK includes an audio and video capture sample application called AMCap, as well as the sample's source code. Internally, AMCap uses the ICreateDevEnum interface to construct a list of a system's capture devices. In the application itself, you can access the list of devices from the Devices menu.

The code that builds AMCap's enumerated list of devices is its InitCapFilters function. This function demonstrates a typical way to enumerate filters, for both former and current hardware devices. For the sake of brevity, the following code walk-through contains no error-checking code; for the complete version, see the Amcap.cpp file in the \Samples\DS\Capture directory of the SDK. The AMCap sample uses a global variable, gcap, which is a structure from the Amcap.cpp file that stores a variety of information used by the filter graph. While you generally want to avoid using global variables, this structure does show the amount of information that the filter graph manager handles.


struct _capstuff {
    char szCaptureFile[_MAX_PATH];
    WORD wCapFileSize;
    ICaptureGraphBuilder *pBuilder;
    IVideoWindow *pVW;
    IMediaEventEx *pME;
    IAMDroppedFrames *pDF;
    IAMVideoCompression *pVC;
    IAMVfwCaptureDialogs *pDlg;
    IAMAudioStreamConfig *pASC;
    IAMVideoStreamConfig *pVSC;
    IBaseFilter *pRender;
    IBaseFilter *pVCap, *pACap;
    IGraphBuilder *pFg;
    IFileSinkFilter *pSink;
    BOOL fCaptureGraphBuilt;
    BOOL fPreviewGraphBuilt;
    BOOL fCaptureGraphRunning;
    BOOL fPreviewGraphRunning;
    BOOL fCapAudio;
    int  iVideoDevice;
    int  iAudioDevice;
    double FrameRate;
    BOOL fWantPreview;
    long lCapStartTime;
    long lCapStopTime;
} gcap;

InitCapFilters starts by defining some basic return and error-checking variables. AMCap uses the uIndex value to loop through the system's hardware devices later.

BOOL InitCapFilters()
{
    HRESULT hr;
    BOOL f;
    UINT uIndex = 0;

The MakeBuilder function call creates a filter graph builder. You can find the MakeBuilder function in Amcap.cpp.

    f = MakeBuilder();

The next section handles the video capture device enumeration; this code is very similar to the code description from the How to Enumerate Hardware Devices section. It first declares an ICreateDevEnum pointer, then uses CoCreateInstance to create an enumerator for system hardware devices.

    ICreateDevEnum *pCreateDevEnum;
    hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
                          IID_ICreateDevEnum, (void**)&pCreateDevEnum);

After it has a device enumerator, AMCap creates an enumerator specifically for video capture devices by passing the CLSID_VideoInputDeviceCategory class identifier to ICreateDevEnum::CreateClassEnumerator. It can now use the IEnumMoniker pointer to access the enumerated list of capture devices.

    IEnumMoniker *pEm;
    hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEm, 0);
    pCreateDevEnum->Release();			// We don't need the device enumerator anymore
    pEm->Reset();						// Go to the start of the enumerated list

Now AMCap needs the actual device; it calls IEnumMoniker::Next to move through the device list, and then points pM to each device by calling IMoniker::BindToObject, which also loads the device's properties (CLSID, FriendlyName, and DevicePath) from the registry. If you do not want to automatically create the filter associated with the device, use IMoniker::BindToStorage instead of BindToObject.

    ULONG cFetched;
    IMoniker *pM;					// This will access the actual devices
    gcap.pVCap = NULL;
    while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)
    {
if ((int)uIndex == gcap.iVideoDevice) {	// This is the one we want.  Instantiate it.
	    hr = pM->BindToObject(0, 0, IID_IBaseFilter, (void**)&gcap.pVCap);
                    pM->Release();			// We don't need the moniker pointer anymore
	    break;
	}
                pM->Release();
	uIndex++;
    }
    pEm->Release();				// We've got the device; don't need the enumerator anymore

After AMCap has a device, it retrieves the interface pointers to measure frames, get the driver name, and get the capture size. AMCap stores each pointer in the gcap global structure.

    // We use this interface to get the number of captured and dropped frames
    gcap.pBuilder->FindCaptureInterface(gcap.pVCap,
				IID_IAMDroppedFrames, (void **)&gcap.pDF);

    // We use this interface to get the name of the driver
    gcap.pBuilder->FindCaptureInterface(gcap.pVCap,
				IID_IAMVideoCompression, (void **)&gcap.pVC);

    // We use this interface to set the frame rate and get the capture size
    gcap.pBuilder->FindCaptureInterface(gcap.pVCap,
				IID_IAMVideoStreamConfig, (void **)&gcap.pVSC);

AMCap then gets the media type and sizes the display window to match the size of the video format.

  AM_MEDIA_TYPE *pmt;
    gcap.pVSC->GetFormat(&pmt);			// Current capture format

    ResizeWindow(HEADER(pmt->pbFormat)->biWidth,
				HEADER(pmt->pbFormat)->biHeight);
    DeleteMediaType(pmt);

This section applies only to earlier Video for Windows devices. Video for Windows devices support a specific set of dialog boxes, which set the video source, format, and display type. For additional information on these dialog boxes, see the IAMVfwCaptureDialogs interface documentation.

    hr = gcap.pBuilder->FindCaptureInterface(gcap.pVCap,
				IID_IAMVfwCaptureDialogs, (void **)&gcap.pDlg);
    if (hr != NOERROR) {
	ErrMsg("Error %x: Cannot find VCapture:IAMVfwCaptureDialogs", hr);
    }

Now that AMCap has the video capture device and its relevant information, it repeats the process with the audio devices and stores the information in the global structure. Note that it calls ICreateDevEnum::CreateClassEnumerator with the CLSID_AudioInputDeviceCategory CLSID to enumerate audio hardware devices.

    hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
                          IID_ICreateDevEnum, (void**)&pCreateDevEnum);
    uIndex = 0;
    hr = pCreateDevEnum->CreateClassEnumerator(CLSID_AudioInputDeviceCategory,
								&pEm, 0);
    pCreateDevEnum->Release();

    pEm->Reset();
    gcap.pACap = NULL;
    while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)
    {
	if ((int)uIndex == gcap.iAudioDevice) {		        // this is the one we want
	    hr = pM->BindToObject(0, 0, IID_IBaseFilter, (void**)&gcap.pACap);
                    pM->Release();
	    break;
	}
                pM->Release();
	uIndex++;
    }
    pEm->Release();

AMCap also repeats the process of retrieving the format interface, this time for the audio device.

    hr = gcap.pBuilder->FindCaptureInterface(gcap.pACap,
				IID_IAMAudioStreamConfig, (void **)&gcap.pASC);
}

How to Store DirectShow Filter Properties Persistently

The Win32 IPropertyBag and IPersistPropertyBag interfaces store and retrieve groups ("bags") of properties for developer-specified objects. Properties stored by these interfaces are persistent; that is, they remain consistent between different instantiations of the same object. Filters can store their properties (CLSID, FriendlyName, and DevicePath) persistently. After a filter stores its properties, DirectShow automatically retrieves them whenever it instantiates the filter. To add this functionality to your filter, implement the IPersistPropertyBag interface and its Load method. Your implementation of the Load method should call the IPropertyBag::Read method to load the filter's properties into a Win32 VARIANT variable, and then initialize its input and output pins.

The following code sample demonstrates how the DirectShow VfWCapture filter implements the IPersistPropertyBag::Load method. Remember that your filter must supply a valid IPropertyBag pointer to hold the filter's properties during execution. You can specify an error log to trap errors generated by the filter's properties, although you can pass in a null value to ignore error reporting.


STDMETHODIMP CVfwCapture::Load(LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog)
{
    HRESULT hr;
    CAutoLock cObjectLock(m_pLock);		// Locks the object; automatically unlocks it in the destructor.

    if (m_pStream)				// If the filter already exists for this stream
	return E_UNEXPECTED;

    VARIANT var;				// VARIANT from Platform SDK
    var.vt = VT_I4;				// four-byte integer (long)
    hr = pPropBag->Read(L"VFWIndex", &var, 0);	// VFWIndex is the private name used by the Vidcap Class Manager to refer to the VFW Capture filter
    if(SUCCEEDED(hr))				// If it read the properties successfully
    {
        hr = S_OK;				// Defaults return value to S_OK
        m_iVideoId = var.lVal;			// Stores the specified hardware device number
        CreatePins(&hr);				// Inits the pins, replacing the return value if necessary
    }
    return hr;					// Returns S_OK or an error value, if CreatePins failed
}

© 1997 Microsoft Corporation. All rights reserved. Terms of Use.