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.
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 ");
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:
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);
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.
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");
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:
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.
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:
IBaseFilter* Device_Init(IMoniker*,IBaseFilter*);
void Device_Addition(IGraphBuilder*,IBaseFilter*,BSTR);
void Device_Connect(IBaseFilter*,IBaseFilter*);
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)
{
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); 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);
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.
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;
IPin *pIn = NULL, *pOut = NULL;
These pins need to be accessed, and this is done by an enumeration:
hr = pInputDevice->EnumPins(&pInputPin);
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.
hr = pInputDevice->FindPin(L"Capture",&pIn);
Now, the two pins are ready to be connected, and are connected by the following code:
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(); if(SUCCEEDED(hr))
{
cout<<"You must be listening to something!!!"<<endl;
}
else HR_Failed(hr);
}
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)
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); int strlenDevice = SysStringLen(bstrDevice); char* chrFilter = (char*)malloc(strlenFilter+1); char* chrDevice = (char*)malloc(strlenDevice+1); int j = 0;
if (strlenFilter!=strlenDevice)
flag = false;
else
{
for(; j < strlenFilter;j++)
{
chrFilter[j] = (char)bstrFilter[j]; chrDevice[j] = (char)bstrDevice[j]; cout<<j;
}
chrFilter[strlenFilter] = '\0'; chrDevice[strlenDevice] = '\0'; for(j=0; j < strlenFilter;j++) {
if(chrFilter[j] != chrDevice[j])
flag = false;
}
if(flag == true && j == strlenFilter-1)
flag = true; }
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.