Click here to Skip to main content
Click here to Skip to main content

Audio Capture with DirectShow - Part 1

, 11 Feb 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
A console program that captures audio but does not save it.

Introduction

This is the first part of a two part tutorial on capturing audio via DirectShow. The first part simply shows how to connect an input device with an output device. The first part does not save the data coming through the input device; the saving portion is being left out for the second part of the tutorial.

What You Should Know First

I have written two small tutorials on how to read audio/video devices installed on a system. For those who do not know how to get hold of such devices, please take a look at these articles:

This would make it easier for you to understand this tutorial, which uses a function that is explained in the previous two tutorials, and which I will not be explaining here. Plus, a bit of understanding of COM programming is always helpful.

How to Use the Program

This is not a program that you can just download and compile and run! Rather, this tutorial will take you through the steps of how to compile the program, and at the end, you will understand how and why GraphEdit and the properties of the filters shown in GraphEdit are being used in the program. The most important step would be changing the values of some variables. This step is very, very important, and must be followed in order to run the program correctly. And, in the end, you will know how to connect input pins to output pins.

First Open GraphEdit!

Step 1: Add an Audio Capture Source

I wrote this in a rather highly un-professional way of building the program. The very simple reason being that I wanted readers to have a real "look-and-feel" of what GrapEdit.exe shows when filters are accessed, and what properties are being used during a call to the various devices. What this means is that when you place a filter, such as an Audio Capture Source, what GraphEdit shows is a filter along with its properties. Therefore, the first step is to open GraphEdit and place a filter from the Audio Capture Source category. Shown below is a snapshot of the Audio Capture Sources on my system. Look at the red square that is around one of the filters. This is the mic that resides on the front panel of my system, and is shown as "Front Mic (IDT High Definition" in GraphEdit.

mic.jpg

Step 2: Change of Variable Values

As you can see, my system has a filter (or a device) with the "FriendlyName" of "Front Mic (IDT High Definition". Read carefully as I have deliberately written the two terms in double quoted marks. The FriendlyName of the second input device in the list is "Front Mic (IDT High Definition" which happens to be on my system. For your system, you would probably, if not definitely, see something else. I am using this device as the audio input device in my code. A line which resides in the code is as follows:

bstrDeviceName =(L"Front Mic (IDT High Definition ");
// device name as seen in Graphedit.exe

The above line of code will allow the program to use the front mic of my system for audio input. What you need to do is replace this string "Front Mic (IDT High Definition " with the FriendlyName that is being "shown" by GraphEdit on your system. Yes, include the space at the end if it is there, like I have done. After placing an audio capture source that GraphEdit has on offer, the next step would be to look at what is the capturing pin in that source. Shown below is what is available on my system:

capture.jpg

You can see the red square and the exact term "Capture" that is also being used in the code:

hr = pInputDevice->FindPin(L"Capture",&pIn);
//Get hold of the pin "Capture", as seen in GraphEdit

It's a jump in the code from main(), but hold on, here the "Capture" property shown by GraphEdit is actually a 'pin', and needs to be enumerated and initialized in order to use it. Whatever be shown by GraphEdit on your system, you should change the value of the variable:

FindPin(L"Capture",&pIn);

Step 3: Add Audio Renderers

Now, you need to add an audio rendering source which, in my case, is going to be the one shown by the red square.

headphones.jpg

Step 4: Change of Variable Values

Just like in step 1, here, you would need to change the value available in GraphEdit and modify the value in the following line of code; this is the head-phone (HP) on my system:

bstrDeviceName = (L"Speakers/HP (IDT High Definition");
// device name as seen in Graphedit.exe

Now, just like step 3, you would change the value in the following line of code:

hr = pOutputDevice->FindPin(L"Audio Input pin (rendered)",&pOut);

A peek into GraphEdit shows the following:

audio_input.jpg

After making a change to the code in your program, it should be ready to compile, but first, try connecting the two sources, Audio capture and Audio renderer, in GraphEdit, and hit the Play button to test the graph. You should hear whatever is being said on the mic in the speaker.

connected.jpg

Finally, the Rest of the Code!

Well, up to this point, you may have wondered how it is quite a puzzle to fix the variable values. But, this is because I want the coders to have a real taste of GraphEdit, the filters and their properties, and how to use these filter properties inside the code.

I am not going to discuss the code which enumerates the audio sources. The code which I am going to explain are of these functions:

//Function to initialize Input/Output devices
IBaseFilter* Device_Init(IMoniker*,IBaseFilter*);
//Function to add device to graph
void Device_Addition(IGraphBuilder*,IBaseFilter*,BSTR);
//Function to connect the two devices, in this case input and output
void Device_Connect(IBaseFilter*,IBaseFilter*);
//Function to run the graph
void Run_Graph(IMediaControl*);

The function Device_Init takes a single pointer variable which is a reference to IBaseFilter. The IBaseFilter interface allows exclusive access to a specific device represented by the pDeviceMonik pointer. In this case, the IBaseFilter references are pInputDevice and pOutputDevice. These two variables retain the address of the input and output devices that were accessed by the Device_Read(...) function.

IBaseFilter* Device_Init(IBaseFilter* pDevice)
{
 //Instantiate the device
 hr = pDeviceMonik->BindToObject(NULL, NULL,
                   IID_IBaseFilter,(void**)&pDevice);
 if (SUCCEEDED(hr))
 {
  cout<<"Device initiation successful..."<<endl;
 }
 else HR_Failed();
  hr = pGraph->AddFilter(pDevice,bstrDeviceName);// add to Graph
 if (SUCCEEDED(hr))
 {
  cout<<"Device addition to graph successful..."<<endl;
 }
 else HR_Failed();
 return pDevice;
}

The function Device_Addition(...); takes pointers to the IBuilderGraph, IBaseFilter, and BSTR types to actually add the audio devices to the graph. Here is a call to add the input device to the graph:

Device_Addition(pGraph,pInputDevice,bstrDeviceName);
//add device to graph
void Device_Addition(IGraphBuilder* pGraph,IBaseFilter* pDevice,BSTR bstrName)
{
    HRESULT hr;
    hr = pGraph->AddFilter(pDevice,bstrName);
    if(SUCCEEDED(hr))
    {
     wcout<<"Addition of "<<bstrName<<" successful..."<<endl;
    }
     else HR_Failed(hr);
}

After making calls for output devices similar to those for input devices, we can now connect the two. In GraphEdit, we connect the two filters (devices) through their pins. Here, we do it manually in the Device_Connect(...) function.

//Connect input to output
Device_Connect(pInputDevice,pOutputDevice);

The function Device_Connect(...) takes the two input devices and connects them. Here, we first need to enumerate the input and output device pins:

IEnumPins *pInputPin = NULL,*pOutputPin  = NULL;
// Pin enumeration

IPin *pIn = NULL, *pOut = NULL;// Pins

These pins need to be accessed, and this is done by an enumeration:

hr = pInputDevice->EnumPins(&pInputPin);// Enumerate the pin

The above is also repeated for pOutputDevice. The next step is accessing the enumerated pins, which is done by a call to FindPin(...). Remember when I said "It's a jump in the code from main()"? This is the point where the jump has to be made. The pins that are exposed by the device drivers are shown in GraphEdit, and that is why we had to change the values so that we correctly access the pins of the specific device. This method takes a string argument, and searches whether there is a pin by the name of either "Capture" or "Audio Input pin (rendered)" for the code working on my system. After the changes you make, the string values would certainly be different for your system.

//Get hold of the pin "Capture", as seen in GraphEdit
hr = pInputDevice->FindPin(L"Capture",&pIn);

Now, the two pins are ready to be connected, and are connected by the following code:

//Connect the input pin to output pin
hr = pIn->Connect(pOut,NULL);

The last and final step is to run the graph. This is done by the reference pointer variable pControl of type IMediaControl. This interface basically allows the running, stopping, and various other control functionalities of the graph.

void Run_Graph(IMediaControl* pControl)
{
    HRESULT hr;
    hr = pControl->Run();// Now run the graph, i.e. start listening!
    if(SUCCEEDED(hr))
    {
      cout<<"You must be listening to something!!!"<<endl;
    }
    else HR_Failed(hr);
}

console.jpg

The last step completes the code, and hopefully there will be a successful run!

The code is not professional by any means, and should be optimized whenever this is a need for a professional approach. The task was to explain the code in the simplest possible way, and to cater for very limited errors. The error handling part is very basic, and should not be used for professional applications as it just displays an error text. I expect bugs to appear, and will highly appreciate changes to the code itself!

A Note on bool Bstr_Compare(BSTR,BSTR)

I had to update the previous code. There was a programming error which was pointed out by Igorka, which I highly appreciate, and I have fixed the error.

if(Bstr_Compare(varName.bstrVal,bstrDeviceName) == true)
//make a comparison

This calls the function Bstr_Compare(...) and checks for the returned boolean value. If 'true' was returned, it means the required device was found.

Rest of the code for Bstr_Compare:

bool Bstr_Compare(BSTR bstrFilter,BSTR bstrDevice)
{
     bool flag = true;
     int strlenFilter = SysStringLen(bstrFilter);//set string length
     int strlenDevice = SysStringLen(bstrDevice);//set string length
     char* chrFilter = (char*)malloc(strlenFilter+1);// allocate memory
     char* chrDevice = (char*)malloc(strlenDevice+1);// allocate memory
     int j = 0;

     if (strlenFilter!=strlenDevice)
     //if the strings are of not the same length, 
     //means they totall different strings
          flag = false;
          //sety flag to false to indicate "not-same" strings
     else
     {
          for(; j < strlenFilter;j++)
          //now, copy 1 by 1 each char 
          //to chrFilter and chrDevice respectively
          {
               chrFilter[j] = (char)bstrFilter[j];//copy
               chrDevice[j] = (char)bstrDevice[j];//copy
               cout<<j;
          }
          chrFilter[strlenFilter] = '\0';//add terminating character
          chrDevice[strlenDevice] = '\0';//add terminating character
          for(j=0; j < strlenFilter;j++)//check loop
          {
               if(chrFilter[j] != chrDevice[j])
               //check if there are chars that are not samne
                    flag = false;
                    //if chars are not same, set flag 
                    //to false to indicate "not-same" strings
          }

          if(flag == true && j == strlenFilter-1)
          //see if we went through the 'check loop' 
               flag = true;//means strings are same
     }
     return flag;
}

The Code is Built with the Following

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

References

History

  • 7th December, 2008: Initial post.
  • 13th December, 2008: Article updated.
    • Added new function Bstr_Compare(...) that correctly compares two BSTR type strings.
    • Added the functions SysAllocString(...) and SysFreeString(...) to correctly allocate string values to BSTR type variables and free them, respectively.
  • 8th February, 2008: Aritcle updated.
    • Fixed Bstr_Compare(...) that correctly compares two BSTR type strings.

License

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

Share

About the Author

wajih_ullah
Engineer -
Pakistan Pakistan
-I am a die hard fan of C/C++ programming. It is poetry and I love the poems of C++.
-I tell people that C# is "NOTHING" compared to C++ and I always say that!
 
My interest lie in Signal Processing, Image Processing,Video Processing, Pattern Recognition, DSP, Communications. Though I have no ONE stop in terms of interests.

Comments and Discussions

 
QuestionThanks its really great tutorial.. But i'm getting an error Pinmemberanchal_scorpio18-Nov-14 0:06 
QuestionThanks it really help a lot,but I still have some problem PinmemberShane_Wayne15-Jul-12 21:38 
AnswerRe: Thanks it really help a lot,but I still have some problem Pinmemberwajih_ullah15-Sep-12 1:13 
QuestionBstr_Compare Pinmemberigorka3-Feb-09 6:08 
AnswerRe: Bstr_Compare Pinmemberwajih_VCP3-Feb-09 9:10 
GeneralWindows Server dev PinmemberMember 145512523-Dec-08 5:53 
GeneralRe: Windows Server dev Pinmemberwajih_VCP23-Dec-08 7:38 
GeneralNice PinmemberJust someone else9-Dec-08 3:06 
GeneralRe: Nice Pinmemberwajih_VCP9-Dec-08 6:43 
GeneralAwesome! PinmemberRene Pilon7-Dec-08 15:54 
GeneralRe: Awesome! Pinmemberwajih_VCP7-Dec-08 21:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141223.1 | Last Updated 11 Feb 2009
Article Copyright 2008 by wajih_ullah
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid