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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (11投票s)

2008 年 12 月 7 日

CPOL

8分钟阅读

viewsIcon

108796

downloadIcon

4747

捕获音频但不保存的控制台程序。

引言

这是关于通过 DirectShow 捕获音频的两部分教程的第一部分。第一部分仅展示如何连接输入设备和输出设备。第一部分不保存来自输入设备的数据;保存部分将留到教程的第二部分。

你应该先知道什么

我写了两篇关于如何读取系统中安装的音频/视频设备的小教程。对于那些不知道如何获取此类设备的人,请参考以下文章:

这将使您更容易理解本教程,本教程使用了一个在前面两篇教程中解释过的函数,而我在这里不会再次解释。此外,对 COM 编程的一点了解总是很有帮助的。

如何使用程序

这并不是一个您可以直接下载、编译和运行的程序!相反,本教程将带您逐步完成编译程序的过程,最后,您将理解为什么以及如何在程序中使用 GraphEditGraphEdit 中显示的过滤器的属性。最重要的一步是更改某些变量的值。这一步非常、非常重要,必须遵循才能正确运行程序。最后,您将知道如何连接输入引脚到输出引脚。

首先打开 GraphEdit!

步骤 1:添加音频捕获源

我以一种很不专业的方式编写了这个程序。非常简单的原因是我想让读者真正“感受”到 GrapEdit.exe 在访问过滤器时显示的内容,以及在调用各种设备时使用的属性。这意味着当您放置一个过滤器(如音频捕获源)时,GraphEdit 显示的是一个带有其属性的过滤器。因此,第一步是打开 GraphEdit 并从“音频捕获源”类别中放置一个过滤器。下面是我系统上音频捕获源的快照。请看围绕其中一个过滤器的红色方框。这是我系统前面板上的麦克风,在 GraphEdit 中显示为“Front Mic (IDT High Definition”。

mic.jpg

步骤 2:更改变量值

正如您所见,我的系统有一个“FriendlyName”为“Front Mic (IDT High Definition”的过滤器(或设备)。请仔细阅读,因为我故意将这两个词用双引号括起来。列表中第二个输入设备的 FriendlyName 是“Front Mic (IDT High Definition”,碰巧在我系统上是这样。对于您的系统,您可能,如果不是肯定,会看到其他东西。我将在我的代码中使用此设备作为音频输入设备。代码中的一行如下:

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

上面的代码行将允许程序使用我系统的前置麦克风进行音频输入。您需要做的是将字符串“Front Mic (IDT High Definition 替换为 GraphEdit 在您的系统上“显示”的 FriendlyName。是的,如果末尾有空格,请像我一样包含它。放置 GraphEdit 提供的音频捕获源后,下一步是查看该源中的捕获引脚是什么。下面是我系统上的可用内容:

capture.jpg

您可以看到红色方框以及代码中也使用的确切术语“Capture”

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

这是从 main() 到代码的一个跳转,但请稍等,这里的 GraphEdit 显示的“Capture”属性实际上是一个‘引脚’,需要枚举和初始化才能使用。无论 GraphEdit 在您的系统上显示什么,您都应该更改变量的值

FindPin(L"Capture",&pIn);

步骤 3:添加音频渲染器

现在,您需要添加一个音频渲染源,对我来说,这将是我用红色方框圈出的那个。

headphones.jpg

步骤 4:更改变量值

就像在步骤 1 中一样,在这里,您需要更改 GraphEdit 中提供的值,并修改以下代码行中的值;这是我系统上的耳机(HP)

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

现在,就像步骤 3 一样,您将更改以下代码行中的值

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

查看 GraphEdit 会看到以下内容

audio_input.jpg

在更改程序中的代码后,它应该可以编译了,但首先,尝试在 GraphEdit 中连接两个源,音频捕获和音频渲染器,然后点击播放按钮来测试图。您应该能听到麦克风发出的声音通过扬声器播放出来。

connected.jpg

最后,剩余的代码!

好吧,到目前为止,您可能一直在想,固定变量值是一件多么棘手的事情。但这正是我希望编码人员能够真正体验 GraphEdit、过滤器及其属性,以及如何在代码中使用这些过滤器属性的原因。

我将不讨论枚举音频源的代码。我将解释的代码是这些函数:

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

函数 Device_Init 接受一个指向 IBaseFilter 的指针变量。IBaseFilter 接口允许独占访问由 pDeviceMonik 指针表示的特定设备。在这种情况下,IBaseFilter 引用是 pInputDevicepOutputDevice。这两个变量保留了 Device_Read(...) 函数访问的输入和输出设备的地址。

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

函数 Device_Addition(...) 接受指向 IBuilderGraphIBaseFilterBSTR 类型的指针,以实际将音频设备添加到图中。这是将输入设备添加到图中的调用:

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

在为输出设备进行与输入设备类似的调用后,我们现在可以连接这两个设备。在 GraphEdit 中,我们通过引脚连接两个过滤器(设备)。在这里,我们在 Device_Connect(...) 函数中手动完成。

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

函数 Device_Connect(...) 接受两个输入设备并连接它们。在这里,我们首先需要枚举输入和输出设备引脚:

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

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

需要访问这些引脚,这可以通过枚举来完成:

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

以上内容也为 pOutputDevice 重复。下一步是访问枚举到的引脚,这是通过调用 FindPin(...) 来完成的。还记得我说过“这是从 main() 到代码的一个跳转”吗?这就是需要跳转的地方。设备驱动程序暴露的引脚在 GraphEdit 中显示,这就是为什么我们需要更改值以便正确访问特定设备的引脚。此方法接受一个字符串参数,并搜索是否存在名为“Capture”或“Audio Input pin (rendered)”的引脚,以便在我的系统上运行代码。在您进行更改后,您的系统上的字符串值肯定会有所不同。

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

现在,这两个引脚已准备好连接,并通过以下代码连接:

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

最后一步是运行图。这是通过类型为 IMediaControl 的引用指针变量 pControl 来完成的。此接口基本上允许图的运行、停止以及各种其他控制功能。

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

最后一步完成了代码,希望能够成功运行!

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

关于 bool Bstr_Compare(BSTR,BSTR) 的说明

我不得不更新之前的代码。有一个编程错误是 Igorka 指出的,我非常感谢,并且我已经修复了这个错误。

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

这会调用函数 Bstr_Compare(...) 并检查返回的布尔值。如果返回‘true’,则意味着找到了所需的设备。

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

代码使用以下方式构建

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

参考文献

历史

  • 2008 年 12 月 7 日:初始发布。
  • 2008 年 12 月 13 日:文章更新。
    • 添加了新函数 Bstr_Compare(...),可以正确比较两个 BSTR 类型字符串。
    • 添加了函数 SysAllocString(...)SysFreeString(...),分别用于正确分配 BSTR 类型变量的字符串值并释放它们。
  • 2008 年 2 月 8 日:文章更新。
    • 修复了 Bstr_Compare(...),该函数可以正确比较两个 BSTR 类型的字符串。
© . All rights reserved.