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






4.89/5 (11投票s)
捕获音频但不保存的控制台程序。
引言
这是关于通过 DirectShow 捕获音频的两部分教程的第一部分。第一部分仅展示如何连接输入设备和输出设备。第一部分不保存来自输入设备的数据;保存部分将留到教程的第二部分。
你应该先知道什么
我写了两篇关于如何读取系统中安装的音频/视频设备的小教程。对于那些不知道如何获取此类设备的人,请参考以下文章:
这将使您更容易理解本教程,本教程使用了一个在前面两篇教程中解释过的函数,而我在这里不会再次解释。此外,对 COM 编程的一点了解总是很有帮助的。
如何使用程序
这并不是一个您可以直接下载、编译和运行的程序!相反,本教程将带您逐步完成编译程序的过程,最后,您将理解为什么以及如何在程序中使用 GraphEdit 和 GraphEdit 中显示的过滤器的属性。最重要的一步是更改某些变量的值。这一步非常、非常重要,必须遵循才能正确运行程序。最后,您将知道如何连接输入引脚到输出引脚。
首先打开 GraphEdit!
步骤 1:添加音频捕获源
我以一种很不专业的方式编写了这个程序。非常简单的原因是我想让读者真正“感受”到 GrapEdit.exe 在访问过滤器时显示的内容,以及在调用各种设备时使用的属性。这意味着当您放置一个过滤器(如音频捕获源)时,GraphEdit 显示的是一个带有其属性的过滤器。因此,第一步是打开 GraphEdit 并从“音频捕获源”类别中放置一个过滤器。下面是我系统上音频捕获源的快照。请看围绕其中一个过滤器的红色方框。这是我系统前面板上的麦克风,在 GraphEdit 中显示为“Front Mic (IDT High Definition”。
步骤 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”
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:添加音频渲染器
现在,您需要添加一个音频渲染源,对我来说,这将是我用红色方框圈出的那个。
步骤 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 会看到以下内容
在更改程序中的代码后,它应该可以编译了,但首先,尝试在 GraphEdit 中连接两个源,音频捕获和音频渲染器,然后点击播放按钮来测试图。您应该能听到麦克风发出的声音通过扬声器播放出来。
最后,剩余的代码!
好吧,到目前为止,您可能一直在想,固定变量值是一件多么棘手的事情。但这正是我希望编码人员能够真正体验 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
引用是 pInputDevice
和 pOutputDevice
。这两个变量保留了 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(...)
接受指向 IBuilderGraph
、IBaseFilter
和 BSTR
类型的指针,以实际将音频设备添加到图中。这是将输入设备添加到图中的调用:
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);
}
最后一步完成了代码,希望能够成功运行!
这段代码绝不专业,在需要专业方法时应进行优化。任务是以最简单的方式解释代码,并处理非常有限的错误。错误处理部分非常基础,不应用于专业应用程序,因为它只显示错误文本。我预计会出现 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
类型的字符串。