Click here to Skip to main content
15,867,568 members
Articles / Multimedia / DirectX

Audio Capture with DirectShow - Part 2

Rate me:
Please Sign up or sign in to vote.
4.38/5 (6 votes)
10 Feb 2009CPOL5 min read 74.3K   2.7K   40   16
A console program that captures audio and saves it.

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.

main_graph.jpg

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

C++
//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*); 
//Function to compare BSTR strings
bool Bstr_Compare(BSTR,BSTR);

Functions in this tutorial (including new and changed)

C++
//Function to connect the two devices
void Device_Connect(IBaseFilter*,IBaseFilter*,BSTR,BSTR);
//Function to access default WAV file
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

C++
void Device_Connect(IBaseFilter* pInputDevice,IBaseFilter* pOutputDevice)
{
    HRESULT hr;
    IEnumPins *pInputPin = NULL,*pOutputPin  = NULL;// Pin enumeration
    IPin *pIn = NULL, *pOut = NULL;// Pins

    hr = pInputDevice->EnumPins(&pInputPin);// Enumerate the pin
    if(SUCCEEDED(hr))
    {
        cout<<"Input Pin Enumeration successful..."<<endl;
               //Get hold of the pin "Capture", as seen in GraphEdit
        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);//Enumerate the pin
    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);    //Connect the input pin to output pin
    if(SUCCEEDED(hr))
        {
            cout<<"Pin connection successful..."<<endl;
        }
    else HR_Failed(hr);
    
}

Device_Connect(...) from Part 2

C++
void Device_Connect(IBaseFilter* pInputDevice,IBaseFilter* pOutputDevice,
                    BSTR bstrInputPin,BSTR bstrOutputPin)
{
    HRESULT hr;
    IEnumPins *pInputPin = NULL,*pOutputPin  = NULL;// Pin enumeration
    IPin *pIn = NULL, *pOut = NULL;// Pins

    hr = pInputDevice->EnumPins(&pInputPin);// Enumerate the pin
    if(SUCCEEDED(hr))
    {
        cout<<"Input Pin Enumeration successful..."<<endl;
        //Get hold of the pin as seen in GraphEdit
        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);//Enumerate the pin
    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);    //Connect the input pin to output pin
    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().

C++
//Connect input to output, AudioRecoder WAV
//Dest filter to test.wav i.e. File writer filter
//Input pin name, as seen in GraphEdit
bstrInPinName = SysAllocString(L"Out");
//Output pin name, this one will be input for File writer filter
bstrOutPinName = SysAllocString(L"in");

//connect Device_Connect(pWAVRecorder,pFileWriter,bstrInPinName,bstrOutPinName);

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;
    // pointer to filter that allows data writing
    IFileSinkFilter *pFileSink= NULL;
    hr = pDevice->QueryInterface(IID_IFileSinkFilter, (void**)&pFile);
    if(SUCCEEDED(hr))
    {
        wcout<<"Querying of IFileSinkFilter "<<bstrName<<" successful..."<<endl;
    }
    else HR_Failed(hr);
    //setting the name of the file
    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.

C++
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.

console_output.jpg

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

  • 08/02/2008: Article 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)


Written By
Engineer -
Pakistan Pakistan
Computer Programming

Comments and Discussions

 
QuestionFantastic! Pin
noiti6-Jun-16 6:26
noiti6-Jun-16 6:26 
AnswerRe: Fantastic! Pin
noiti6-Jun-16 17:33
noiti6-Jun-16 17:33 
Questionselect output device Pin
kinani2-Aug-11 16:28
kinani2-Aug-11 16:28 
AnswerRe: select output device Pin
WajihUllahBaig2-Aug-11 19:41
WajihUllahBaig2-Aug-11 19:41 
GeneralRe: select output device Pin
kinani6-Aug-11 15:29
kinani6-Aug-11 15:29 
AnswerRe: select output device Pin
kinani16-Aug-11 0:02
kinani16-Aug-11 0:02 
GeneralRe: select output device Pin
WajihUllahBaig16-Aug-11 5:42
WajihUllahBaig16-Aug-11 5:42 
QuestionAny alternative to 'AudioRecorder WAV Dest' Pin
Rick204724-Jun-09 21:01
Rick204724-Jun-09 21:01 
AnswerRe: Any alternative to 'AudioRecorder WAV Dest' Pin
WajihUllahBaig26-Jun-09 5:43
WajihUllahBaig26-Jun-09 5:43 
Generalbuild errors (and resolution?) Pin
essetesse31-Mar-09 15:52
essetesse31-Mar-09 15:52 
i am using Visual C++ 2008 Express. i got a number of errors and here's what i did about it:

i got an error that the compiler could not find "streams.h"

so i downloaded the platform SDK.....

then i changed the "additional include directories" to "C:\Program Files\Microsoft Platform SDK\Samples\Multimedia\DirectShow\BaseClasses" (it was H:\something)

then i got 2 errors

in ctlutil.h(278) : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int

in tchar.h(26) : fatal error C1189: #error : Need to include strsafe.h after tchar.h

so i commented out line 7 of main.cpp like so:
//#include"streams.h"

then i got this error
LNK1104: cannot open file 'Strmbasd.lib'

so in the property page for linker / input, i got rid of Strmbasd.lib so that "additional dependencies" looks like so: Strmiids.lib Quartz.lib WINMM.LIB

then it builds fine for me.... hope this is helpful to someone else out there Wink | ;) note i ended up commenting out streams.h so i think you can avoid the platform SDK part
GeneralWhy recording length is less...... Pin
RichardWdy2-Mar-09 22:50
RichardWdy2-Mar-09 22:50 
GeneralRe: Why recording length is less...... Pin
WajihUllahBaig3-Mar-09 3:56
WajihUllahBaig3-Mar-09 3:56 
GeneralRe: Why recording length is less...... Pin
RichardWdy3-Mar-09 15:32
RichardWdy3-Mar-09 15:32 
GeneralRe: Why recording length is less...... Pin
cocla15-Apr-09 15:13
cocla15-Apr-09 15:13 
GeneralRe: Why recording length is less...... Pin
RichardWdy18-Apr-09 5:14
RichardWdy18-Apr-09 5:14 
GeneralRe: Why recording length is less...... Pin
cocla18-Apr-09 22:34
cocla18-Apr-09 22:34 

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

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