65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 DirectShow 进行音频捕获 - 第二部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.38/5 (6投票s)

2008年12月16日

CPOL

5分钟阅读

viewsIcon

78216

downloadIcon

2768

一个捕获音频并保存的控制台程序。

引言

这是关于通过 DirectShow 捕获音频的两部分教程的第二部分。第一部分介绍了如何连接输入设备与输出设备。本部分介绍了如何将捕获的音频保存到文件中。此程序提供在保存音频到磁盘的同时进行音频捕获预览的功能。

您应该首先了解

我编写了两个关于如何读取系统中安装的音频/视频设备的简短教程。对于那些不知道如何获取这些设备的人,请参考这些文章。

这将使您更容易理解本教程,因为它使用了在前两个教程中解释过的一个函数,而我在这里不会再解释它。此外,对 COM 编程的理解总是有帮助的。本教程的第一部分可以在以下链接找到。

正在写入 WAV 格式文件

我正在尝试访问一个“wav”文件来写入音频数据。我尝试使用最简单的过滤器来实现这一点。下面的 GraphEdit 图像清楚地标示了将要使用的三个不同过滤器。连接顺序很重要,并且在程序中,顺序也被遵循。您应该首先尝试 GraphEdit,然后尝试示例代码。

main_graph.jpg

在使用代码之前 - 输出文件“test.wav”

在代码成功编译之前,您需要确保您首先拥有一个名为“test.wav”的音频文件。程序查找此文件的默认位置是“..\\test.wav”。所以,请确保您已经使用 Windows 录音机创建了一个文件,或者使用本教程提供的文件。

与之前的代码相比有什么变化

与第一个教程相比,有很多变化。所以,为了跟上进度,我将解释新程序中已更改和新增的内容。

来自上一教程的函数

//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);

本教程中的函数(包括新增和更改的)

//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);

如您所见,`Device_Connect(...)` 函数已更改,并在参数列表中添加了两个 `BSTR` 字符串。这两个字符串代表将要连接的两个引脚的 FriendlyNames。在上一教程中,正在连接的两个引脚(最终,要从中连接两个引脚的两个设备)是麦克风和输出设备,即扬声器、耳机或字符串所代表的任何内容。然而,这一次,由于有三个过滤器需要连接,每个过滤器都有其自己的特定引脚和特定的 FriendlyName;因此,必须通过这两个 `BSTR` 字符串来区分正在连接的设备(即过滤器)。因此,教程的第一部分和第二部分的代码如下。

来自第一部分的 Device_Connect(...)

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(...)

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);
}

如您清楚所见,第二部分中的代码处理 `BSTR` 字符串来连接不同的引脚。带有 `BSTR` 字符串值的 `Device_Connect(...)` 函数从 `main()` 调用。

//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);

在上面的代码中,“AudioRecoder WAV Dest”过滤器(由 `pWAVRecorder` 表示)正在连接到“File Writer”过滤器(由 `pFileWriter` 表示)。引脚成功连接后,程序便可以继续进行。

Add_File (...) 函数

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;
}

`Add_File(...)` 函数将“test.wav”文件添加到过滤器。此函数使用 `IFileSinkFilter` 接口访问 Wav 文件“test.wav”,并在成功访问后返回指针。此函数的调用顺序非常重要。如果我们尝试在调用 `Add_File(...)` 函数之前将 `pFileWriter` 过滤器添加到图中,将会发生错误,程序将无法工作。在使用 GraphEdit 中的 `FileWriter` 过滤器时,这一点解释得最好。当我们尝试从 GraphEdit 将此过滤器添加到图中时,首先发生的是弹出一个对话框,我们必须从中选择输出文件。只有选择文件后,过滤器才会被添加到图中。这里也是一样的。我们首先访问默认输出文件“test.wav”,然后将其添加到图中。

设置文件名的一行代码是我们定义要访问的文件。

hr = pFile->SetFileName(bstrName,NULL);

准备运行

最后,调用 `Run_Graph(...)` 启动整个图。然后,您应该会看到以下屏幕。这是我的系统上程序运行的截图。

console_output.jpg

关于 Wav 文件的说明

请记住,我们正在访问输出文件,而不知道或不更改文件的属性。因此,无论何时使用此程序录制,数据都将从文件的开头写入,文件既不会被截断,也不会完全擦除任何之前的录制。因此,如果您的录制时间小于 test.wav 的原始时长,旧录制的一部分仍会保留。

代码错误

该代码绝不专业,在需要专业方法时应进行优化。任务是以最简单的方式解释代码,并处理非常有限的错误。错误处理部分非常基础,不应用于专业应用程序,因为它仅显示错误文本。我预计会出现 bug,并非常感谢对代码本身的改进!

代码构建如下

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

参考文献

历史

  • 2008/02/08:更新文章。

    修复了 Bstr_Compare(...),该函数可以正确比较两个 BSTR 类型的字符串。

© . All rights reserved.