Introduction
This is the second part of a two part tutorial on capturing audio via DirectShow. The first part shows how to connect an input device with an output device. This part shows how the captured audio can be saved to a file. This program does not provide functionality for simultaneous preview of audio capture while saving it to the disk.
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. The first part of the tutorial is available at the following link:
WAV format file being written
I am trying to access a "wav" file to write the audio data. I have tried to use the simplest of filters in order to do so. The GraphEdit image below clearly indicates the three different filters that are going to be used. The order of connection does matter, and programmatically, the orders are being followed. You should first try GraphEdit, and then try out the sample code.
Before using the code - The output file, "test.wav"
Before the code can be successfully compiled, you need to make sure you first have an audio file called "test.wav". The default location where the program looks for this file is "..\\test.wav". So, please make sure you have created a file with the Windows Sound Recorder, or use the one provided with this tutorial.
What has changed from the previous code
A lot has been changed from the first tutorial. So, to go with the flow, I will explain what has changed and what has been added in the new program.
Functions from the previous tutorial
IBaseFilter* Device_Init(IMoniker*,IBaseFilter*);
void Device_Addition(IGraphBuilder*,IBaseFilter*,BSTR);
void Device_Connect(IBaseFilter*,IBaseFilter*);
void Run_Graph(IMediaControl*);
bool Bstr_Compare(BSTR,BSTR);
Functions in this tutorial (including new and changed)
void Device_Connect(IBaseFilter*,IBaseFilter*,BSTR,BSTR);
IFileSinkFilter* Add_File(IGraphBuilder*,IBaseFilter*,IFileSinkFilter*,BSTR);
As you can see, the function Device_Connect(...)
has been changed and two BSTR
strings were added to the argument list. The two strings represent the FriendlyNames of the two pins that are going to be connected to. In the previous tutorial, the two pins that were being connected (and ultimately, the two devices from which the two pins were to be connected) were the mic and the output device, i.e., speaker, headphone, or whatever the strings represented. This time, however, since there are three filters that need to be connected, each filter has its own specific pin with a specific FriendlyName; so, the devices (i.e., filters) that are being connected must be distinguished via these two BSTR
strings. Therefore, the code from Part 1 and Part 2 of the tutorial follows.
Device_Connect(...) from Part 1
void Device_Connect(IBaseFilter* pInputDevice,IBaseFilter* pOutputDevice)
{
HRESULT hr;
IEnumPins *pInputPin = NULL,*pOutputPin = NULL; IPin *pIn = NULL, *pOut = NULL;
hr = pInputDevice->EnumPins(&pInputPin); if(SUCCEEDED(hr))
{
cout<<"Input Pin Enumeration successful..."<<endl;
hr = pInputDevice->FindPin(L"Capture",&pIn);
if(SUCCEEDED(hr))
{
cout<<"Capture pin found"<<endl;
}
else HR_Failed(hr);
}
else HR_Failed(hr);
hr = pOutputDevice->EnumPins(&pOutputPin); if(SUCCEEDED(hr))
{
cout<<"Output Pin Enumeration successful..."<<endl;
hr = pOutputDevice->FindPin(L"Audio Input pin (rendered)",&pOut);
if(SUCCEEDED(hr))
{
cout<<"Audio Input Pin (rendered) found"<<endl;
}
else HR_Failed(hr);
}
else HR_Failed(hr);
hr = pIn->Connect(pOut,NULL); if(SUCCEEDED(hr))
{
cout<<"Pin connection successful..."<<endl;
}
else HR_Failed(hr);
}
Device_Connect(...) from Part 2
void Device_Connect(IBaseFilter* pInputDevice,IBaseFilter* pOutputDevice,
BSTR bstrInputPin,BSTR bstrOutputPin)
{
HRESULT hr;
IEnumPins *pInputPin = NULL,*pOutputPin = NULL; IPin *pIn = NULL, *pOut = NULL;
hr = pInputDevice->EnumPins(&pInputPin); if(SUCCEEDED(hr))
{
cout<<"Input Pin Enumeration successful..."<<endl;
hr = pInputDevice->FindPin(bstrInputPin,&pIn);
if(SUCCEEDED(hr))
{
wcout<<bstrInputPin<<" Input pin found"<<endl;
}
else HR_Failed(hr);
}
else HR_Failed(hr);
hr = pOutputDevice->EnumPins(&pOutputPin); if(SUCCEEDED(hr))
{
cout<<"Output Pin Enumeration successful..."<<endl;
hr = pOutputDevice->FindPin(bstrOutputPin,&pOut);
if(SUCCEEDED(hr))
{
wcout<<bstrOutputPin<<" Output pin found"<<endl;
}
else HR_Failed(hr);
}
else HR_Failed(hr);
hr = pIn->Connect(pOut,NULL); if(SUCCEEDED(hr))
{
cout<<"Pin connection successful..."<<endl;
}
else HR_Failed(hr);
}
As you can clearly see, the code in the second part handles the BSTR
strings to connect the different pins. The function Device_Connect(...)
with the BSTR
string values is called from the main()
.
bstrInPinName = SysAllocString(L"Out");
bstrOutPinName = SysAllocString(L"in");
In the above code, the "AudioRecoder WAV Dest" filter represented by pWAVRecorder
is being connected to the "File Writer" filter represented by pFileWriter
. The pins' successful connection allows the program to proceed next.
Add_File (...) function
IFileSinkFilter* Add_File(IGraphBuilder *pGraph,IBaseFilter* pDevice,
IFileSinkFilter* pFile,BSTR bstrName)
{
HRESULT hr;
IFileSinkFilter *pFileSink= NULL;
hr = pDevice->QueryInterface(IID_IFileSinkFilter, (void**)&pFile);
if(SUCCEEDED(hr))
{
wcout<<"Querying of IFileSinkFilter "<<bstrName<<" successful..."<<endl;
}
else HR_Failed(hr);
hr = pFile->SetFileName(bstrName,NULL);
if(SUCCEEDED(hr))
{
wcout<<"Setting name to "<<bstrName<<" successful..."<<endl;
}
else HR_Failed(hr);
return pFileSink = pFile;
}
The Add_File(...)
function adds the file "test.wav" to the filter. This function uses the IFileSinkFilter
interface to access the Wav file "test.wav", and returns the pointers after a successful access. This function is very important in the order in which it is being called. If we try to add a pFileWriter
filter to the graph prior to calling the Add_File(...)
function, an error shall occur and the program will not work. This is best explained when we use the FileWriter
filter in GraphEdit. When we try to add this filter to the graph from GraphEdit, the first thing that happens is that we are presented with a dialog box from which we must select the output file. Only after the file selection is the filter added to the graph. It is the same way here. We first access the default output file "test.wav" and then add it to the graph.
The line of code setting the file name is where we define the file to access.
hr = pFile->SetFileName(bstrName,NULL);
Ready to run
And finally, a call to Run_Graph(...)
kick starts the whole graph. And, you should see the following screen. This is a screenshot of the program running on my system.
A note on the Wav file
Remember, we are accessing the output file without knowing or altering the attributes of the file. So, whenever you record using this program, the data is written from the start of the file, and the file is neither truncated, nor is any previous recording fully erased. So, a portion of the old recording will still remain if your record time is lesser than the original time length of test.wav.
Code bugs
The code is not professional by any means, and should be optimized whenever there 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!
The Code is Built with the Following
- Windows Server 2008
- Microsoft Visual Studio 2008
- DirectX 10.1
- Microsoft Windows SDK 6.1
References
History