在一些使用声卡进行音频采集的应用程序中,通常有如下这样的功能需求:程序启动的时候,要求自动为声卡选择某个特定的输入端子——比如一些通用的采集程序,希望将“Line In”作为默认输入;而一些视频会议软件,希望将“Microphone”作为默认输入。遗憾的是,DirectShow并不能直接支持这样的功能。
大家知道,声卡在DirectShow中是以Filter的形式出现的,我们通常称之为Audio Capture Filter。Filter的每个输入Pin即代表声卡的一个输入端子,Pin的名字就是输入端子的名字。对于不同的声卡来说,输入端子的个数和类型都可能是不一样的。以Avance AC97 Audio和SoundMAX Digital Audio两块声卡为例,它们的输入Pin分别为(注:以“索引:Pin的名字”的方式列出):
0:Mono Mix 1:Stereo Mix 2:Aux 3:TV Tuner Audio 4:CD Player 5:Line In 6:Microphone 7: Phone Line
0:CD Player 1:Micro-phone 2:Line-In 3:Mono Out 4:Wave Out Mix
在DirectShow中,可以通过索引的方式枚举出Audio Capture Filter的所有输入Pin,但单纯依靠索引无法识别出Pin代表的具体输入端子类型,而且不同声卡的索引也不具有通用的意义。对于一块未知的声卡,应用程序怎么通过一种统一的方式去选择一个特定类型的输入端子呢?有一个笨办法,那就是猜——尽可能多地猜测输入Pin的名字!以选择“Line In”作为默认输入为例:
// 演示代码1
// We set "Line In" as default, by checking the pin name
if (pinName.CompareNoCase("Line In") == 0 ||
pinName.CompareNoCase("Line-In") == 0 ||
pinName.CompareNoCase("Line_In") == 0 ||
pinName.CompareNoCase("LineIn") == 0 ||
pinName.CompareNoCase("Line") == 0)
{
found = TRUE;
}
代码有点丑陋,但如果没有更好的办法的话,也不失为一种解决方案。(注:《DirectShow实务精选》第2章的AVCap例子采用的就是这样的方法。)有没有更好的办法呢?有!那就要依赖Windows Multimedia API函数的配合了——通过mixerGetDevCaps、mixerGetLineInfo等一系列函数,我们可以首先确切地获得想要的那种输入端子在当前系统、当前声卡上采用的名字,然后再去枚举Audio Capture Filter上的输入Pin,挑选其中一个名字匹配的。使用Windows Multimedia API函数查询特定输入端子的名字的函数GetConnectionName实现如下:
// 演示代码2
// Some frequently-used line type:
// line in -> MIXERLINE_COMPONENTTYPE_SRC_LINE
// microphone -> MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE
// CD Player -> MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC
BOOL GetConnectionName(DWORD inLineType, CString& outName)
{
UINT cMixers = mixerGetNumDevs();
if (cMixers < 1)
{
TRACE("No mixer device present.");
return FALSE;
}
// Open a mixer and determine its capabilities.
HMIXER hMixer;
if (mixerOpen(&hMixer, 0, 0, 0, 0 ) != MMSYSERR_NOERROR)
{
TRACE("Could not open mixer device.");
return FALSE;
}
MIXERCAPS caps;
if (mixerGetDevCaps((UINT)hMixer, &caps, sizeof(MIXERCAPS)) != MMSYSERR_NOERROR)
{
mixerClose(hMixer);
return FALSE;
}
TRACE("Name of device: %s/n", caps.szPname);
MIXERLINE line;
BOOL found = FALSE;
int cDest = caps.cDestinations;
for (int i = 0; i < cDest && !found; i++)
{
line.cbStruct = sizeof(MIXERLINE);
line.dwSource = 0;
line.dwDestination = i;
mixerGetLineInfo((HMIXEROBJ)hMixer, &line, MIXER_GETLINEINFOF_DESTINATION);
// For recording control
if (line.dwComponentType == MIXERLINE_COMPONENTTYPE_DST_WAVEIN)
{
// Enumerate all source connections for this destination line
UINT cConnections = line.cConnections;
for (UINT j = 0; j < cConnections; j++)
{
line.cbStruct = sizeof(MIXERLINE);
line.dwSource = j;
line.dwDestination = i;
mixerGetLineInfo((HMIXEROBJ)hMixer, &line, MIXER_GETLINEINFOF_SOURCE);
// Compare with the user-specified line type
if (line.dwComponentType == inLineType)
{
// Retrieve the connection name
outName = line.szName;
found = TRUE;
break;
}
}
}
}
mixerClose(hMixer);
return found;
}
于是,演示代码1可以修改如下:
// 演示代码3
CString defaultConnection;
GetConnectionName(MIXERLINE_COMPONENTTYPE_SRC_LINE, defaultConnection);
if (pinName == defaultConnection)
{
found = TRUE;
}
显然,第2种方案要优于第1种方案。但再好的解决方案也不如微软更新一下DirectShow SDK,使得Audio Capture Filter上的每个输入Pin上都增加一个接口(如IAMAudioConnection);通过IAMAudioConnection接口,开发者能够很容易地识别出Pin代表的输入端子类型(像Windows Multimedia一样,用一个整型数值来标示不同的输入端子类型)。