Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Reading audio, video devices and codecs in DirectShow

3.93/5 (9 votes)
3 Dec 2008CPOL4 min read 73.8K   2.5K  
How to read audio, video devices and codecs installed on a PC via DirectShow.

Introduction

To get a list of available audio devices, video devices, and codecs on your system, you would require a starting point in DirectX or rather DirectShow. This extremely small program will give you a place to start with. Though, to play audio and videos, we need modifications to our program, and especially, if we are to write a program that would require instant playback and file-saving etc. This small program will, however, give the main starting point for beginners who are interested in leering DirectShow programming with access to input/output devices and audio/video codecs.

Background

DirectX uses the term 'filters' to specify different hardware and software codecs. Whereas, here in the program, I have used the term 'Device'. When I started programming, I never liked to use the term 'filters', and always resorted to the term 'device', so I personally think that some of you feel like that too! There is limited exception handling, as with my previous tutorials. I deliberately did that so that the code would become more user-friendly, instead of being a collection of professional code.

As you know, DirectX is COM based, so a little COM understanding would be good.

Code

There are two simple functions apart from main():

void HR_Failed(HRESULT);
void Device_Reader(GUID,ICreateDeviceEnum);

First, let us see what we have in main(). The main() function of the program holds the initialization code of COM, and declares a couple of variables, 'hr' and 'ICreateDevEnum'. ICreateDevEnum is the interface of which we maintain a reference in the pointer variable *pDeviceEnum. What this interface does is explained after the explanation of the HR_Failed(hr) function.

The HR_Failed() function receives an HRESULT type; the function is called whenever an error occurs. For example:

hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);// Initialise COM
if (SUCCEEDED(hr))
    {
        cout<<"COM Initialisation successful"<<endl;
    }
    else HR_Failed(hr)

In the above lines of code, hr is returned after a call to initialize COM. If initialization fails, a call to HR_Failed(hr) is made. The code inside the function will translate the value of 'hr' to a more human-understandable code (I found the code on MSDN, let me be honest here!).

void HR_Failed(HRESULT hr)
{
    TCHAR szErr[MAX_ERROR_TEXT_LEN];

    DWORD res = AMGetErrorText(hr, szErr, MAX_ERROR_TEXT_LEN);
    if (res == 0)
    {
        StringCchPrintf(szErr, MAX_ERROR_TEXT_LEN, 
                        L"Unknown Error: 0x%2x", hr);
    } 
    MessageBox(0, szErr, TEXT("Error!"), MB_OK | MB_ICONERROR);
    return;
}

The function Device_Reader(GUID,ICreateDevEnum) is responsible for reading or extracting the 'filters', i.e., the audio, video, and codec filters installed on the system. Don't confuse the 'Device' with a codec. DirectX terms it 'filters' and I term it 'devices'. You can happily rename the function to Filter_Reader if that makes you feel easy. In one of my previous articles, I have explained how to read the devices on a system. You can find that topic here, but I will still write a few lines here too as the code in the Device_Reader function is almost the same.

First, we initialize the device enumeration with a call to CoCreateInstance.

hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, 
   IID_ICreateDevEnum, (void **)&pDeviceEnum);//Initialise Device enumerator

We then need to create a device enumeration which allows us to get hold of or "read in" the audio devices that are available on the system. Here, we need to specify the category of devices that we are trying to read or get hold of. We do this enumeration by passing on the GUID for each category as defined by the MSDN documentation. In this case, I have used quite a few: CLSID_AudioInputDeviceCategory, CLSID_AudioCompressorCategory. The name of each GUID refers to a specific category.

The other usefulness of using a device enumeration via Class ID is that suppose we have two sound cards and both of them support the same filters, using this enumeration we can treat them distinctively as separate instances. After the enumeration, we cannot simply use pin properties or access any other methods for the devices in the AudioInputDeviceCategory. To do this, we have to use Monikers. A Moniker is an interface that refers to a specific device, and then we use IMoniker and IPropertyBag to bind devices to a Moniker and read properties of the devices, respectively. The IPropertyBag interface holds the keys to the device properties.

void Device_Reader(GUID DEVICE_CLSID,ICreateDevEnum *pDeviceEnum )
{
    HRESULT hr;
    IMoniker *pDeviceMonik = NULL;// Device moniker
    IEnumMoniker *pEnumCat = NULL;// Device enumeration moniker
    VARIANT varName;

    // Enumerate the specified device, distinguished by DEVICE_CLSID
    hr = pDeviceEnum->CreateClassEnumerator(DEVICE_CLSID, &pEnumCat, 0);

    if (hr == S_OK) 
    {
        ULONG cFetched;
        while (pEnumCat->Next(1, &pDeviceMonik, &cFetched) == S_OK)
        //Pickup as moniker
        {
            IPropertyBag *pPropBag = NULL;
            //bind the properties of the moniker  
            hr = pDeviceMonik->BindToStorage(0, 0, 
                    IID_IPropertyBag,(void **)&pPropBag);
            if (SUCCEEDED(hr))
            {
                // Initialise the variant data type
                VariantInit(&varName);
                //extract the FriendlyName
                // - This is the name we see in GraphEdit
                hr = pPropBag->Read(L"FriendlyName", &varName, 0);
                if (SUCCEEDED(hr))
                {
                    wcout<<varName.bstrVal<<endl;
                    // show the name
                }
                else HR_Failed(hr);
                //clear the variant data type
                VariantClear(&varName);
                pPropBag->Release();//release the properties
            }
            else HR_Failed(hr);
            pDeviceMonik->Release();//release Device moniker
        }
        pEnumCat->Release();//release category enumerator
    }
    else HR_Failed(hr);    
}

The function accepts the GUID and the pointer to the ICreateDevEnum interface. Using the different GUIDs, each call accessed the available audio, video, and codec filters on the system. Below is a screenshot of what is available on my system.

list.jpg

What happens when a filter is not found??

A call to HR_Failed is made, and the following window pops up with the error message 'Incorrect Function'. Every time a whole 'category' of filters is not found, the call to HR_Failed is made, which displays the windows.

error.jpg

Points of interest

GraphEdit has always been the tool for developers to get out of trouble. Though it looks very primitive, it is an absolute beauty. Only after using GraphEdit did I realise that it displays exactly the same "FriendlyName" as seen in the console output. So, be sure you have a go at it. If you cannot find GraphEdit on your system, make sure you have installed the Windows SDK, and look in the 'Bin' directory to find GrapEdit.exe.

The code is built with the following

  • Windows Server 2008
  • MS Visual Studio 2008
  • DirectX 10.1
  • Microsoft Windows SDK 6.1

History

First post. Will be updated if required.

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)