分类:
2011-03-28 13:30:57
为了理解Mixer API是如何工作的,首先我们得弄清楚一个典型声卡的硬件组成。因此非常有必要去建立一个声卡模型,此声卡应拥有多个典型的组件并且这些组件都是相关联的。
让我们看一个典型的、最基本的声卡。首先,如果声卡能够进行数字化录音,那么典型情况下它就有一个Microphone
Input(麦克风传声器,下同)(附有某种前置放大器),同时它还有一个ADC(模数转换器,下同)将麦克风输入的模拟信号转换为数字信号,因此,它就有两个组件——Microphone
Input和ADC。从Microphone
Input组件输入的信号输送到ADC。我们可以使用以下的方块图来表示这两个组件,并表明信号在这两个组件之间的传输关系(通过箭头表示)。
|
一个典型的声卡应该还可以回放数字声音,因此它有一个DAC(数模转换器,下同)组件将数字信号转换回模拟信号,同时它还应有一个Speaker Out(扬声器,下同)(附有某种模拟信号放大器)。因此,它又添加了两个组件——DAC和Speaker Out。从DAC输出的信号输送到Speaker Out中。
|
|
一个典型的声卡或许还有其它组件。例如,它或许有一个能播放MIDI音频的内置声音模块(比如Synth(合成器,下同))。这个组件的声音输出同DAC的输出同样输入到Speaker
Out组件中。因此,我们的方框图现在是如下的样子:
|
|
|
同样的,一个典型的声卡还有一个内部的连接器连接着计算机的CDROM驱动器的声音输出(这样就可以通过扬声器来播放CDROM里的CD)。与Synth和DAC一样,这个组件的输出会输入到Speaker
Out。现在,我们的方块图就成了如下的样子:
alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1027"> |
alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1028"> |
|
|
最后,我们假设这个声卡还有一个Line In(线路输入,下同)组件,因此其它外部的录音机或音频设备或外部的硬件混音器就能够连接到此插孔并将其输入信号数字化。同Microphone
Input组件一样,这个组件的输出会输送到ADC组件。下面是我们最终的方块图,其中包括7个组件(和5个信号流向线图——也就是将各个组件相连的箭头线)
|
|
|
|
|
典型情况下,每个组件都有它自己的参数。例如,Synth通常会有它的音量参数。Internal CD Audio同样有它自己的音量参数。DAC也会有它自己的音量参数。在这种方式下,如果用户同时播放一个Audio CD、一个MIDI文件和一个WAVE文件,他可以分别调整这三个组件输入到Speaker Out的音量。同样的,Speaker Out组件也有它自己的音量参数——控制上述3个输入组件最终输出的主音量。
同样的,Line In和Microphone Input组件也都有各自的音量参数,这样在同时录音的情况下,就可以平衡二者的输入。ADC组件有个主音量参数,它控制着上述2个连接到它的输入组件的录音音量。
一个给定的组件还拥有其它可控的参数。例如,上述每个组件都有各自的用于快速打开或关闭声音的静音(Mute)开关。
混音器设备
系统中每个声卡都有一个与其相连的混音器设备。声卡上所有的组件都由与声卡关联的混音器设备控制。Windows
Mixer API就是用来访问声卡的混音器设备的一组函数。Mixer API有一组函数,可以获取声卡上所有的组件并调整它们的参数。这是Win95/98和WinNT (4.X 及以上版本)新增的一组API,虽然加入到windows 3.1及更早的版本中也可以使用。
注意:有些声卡的设备驱动需要额外的支持才可以协同Mixer API工作。不是所有的Win95和WinNT驱动都支持Mixer API操作。Win3.1驱动不支持Mixer API操作。
在一个计算机中,可能安装有一个以上声卡。你或许已经注意到windows在系统中维护了一组WAVE和MIDI 输入输出的设备列表。既然每个已安装的声卡都有其对应的混音器设备(只要声卡的驱动支持),windows同样也就维护了一组已安装在系统中的混音器设备。例如,如果你有在系统中安装了两块声卡,那么系统中就应该有两个混音器设备(假设两个声卡的驱动都支持Mixer API)。
同WAVE和MIDI输入输出设备一样,windows也会给每个混音器设备赋一个数值ID。因此,系统中的第一个(默认)混音器设备的ID号为0。如果系统中有第二个声卡,那么就有一个ID为1的混音器设备。
打开一个混音器设备
怎样在你的程序中选择一个混音器设备来进行操作?这里有很多种方法,具体要看你想做多炫、多灵活的程序了。
如果你只是想简单的打开首选的混音器设备,那么您可以使用mixerOpen函数,并使设备ID值0,如下:
unsigned long err;
HMIXER mixerHandle;
/* 打开与默认的Audio/MIDI声卡相连的混音器 */
err =
mixerOpen(&mixerHandle, 0, 0, 0, 0);
if (err)
{
printf("ERROR: Can't open Mixer Device! -- %08X\n", err);
}
else
{
/*混音器已打开,你可以此混音器句柄mixerHandle用在其它Mixer API中*/
}
当然,如果没有安装混音器,上述调用将会返回一个错误值,因此要始终检查那个返回值。(调用Mixer API返回的预期错误值已列在头文件MMSYSTEM.H中。不幸的是,不像其它WAVE和MIDI那些更低级的API,这里没有将这些错误值代码转换为描述字符串的API函数)。
但是,什么是首选混音器设备?好,让我来告诉你,那就是第一个安装在系统中的混音器设备。如果系统中只有一个声卡,那么你得到的混音器设备肯定就是首选混音器设备。但是,如果你尝试着去使用第二块声卡上的Wave Output组件,那该怎么做?你肯定不希望使用第一块声卡的混音器设备去控制第二块声卡上的Wave Out组件的音量。(第一块声卡的混音器当然控制不了第二块声卡的Wave
Output组件了)。
但是,你怎样打开与你想要的那块声卡关联的混音器?幸运的是,mixerOpen()函数允许你传递与这个声卡关联的其它设备的ID或句柄来打开混音器。在这种情况下,mixerOpen()函数将会准确无疑的返回与那个声卡的其它设备关联的混音器设备。以下是一个样例程序,展示了如何通过打开默认的WAVE OUT设备(默认声卡上的WAVE OUT设备)获取混音器设备的句柄:
unsigned long err;
HMIXER mixerHandle;
WAVEFORMATEX
waveFormat;
HWAVEOUT hWaveOut;
/*
打开默认的WAVE Out设备, 指定回调函数,确保waveFormat已被正确的初始化
*/
err =
waveOutOpen(&hWaveOut, WAVE_MAPPER, &waveFormat, (DWORD)WaveOutProc, 0,
CALLBACK_FUNCTION);
if (err)
{
printf("ERROR: Can't open WAVE Out Device! -- %08X\n", err);
}
else
{
/*
已打开与WAVE OUT设备关联的混音器.
注意我传递的是通过waveOutOpen()返回的句柄
*/
err = mixerOpen(&mixerHandle, hWaveOut, 0, 0, MIXER_OBJECTF_HWAVEOUT);
if (err)
{
printf("ERROR: Can't open Mixer Device! -- %08X\n", err);
}
}
上述代码的关键不仅仅是传递waveOutOpen()(或者waveInOpen(), 或者midiOutOpen(),或者midiInOpen())返回的句柄,同样要注意的是mixerOpen()的最后一个参数必须是MIXER_OBJECTF_HWAVEOUT (或者MIXER_OBJECTF_HWAVEIN,或者MIXER_OBJECTF_HMIDIOUT, 或者MIXER_OBJECTF_HMIDIIN),用于指明传递的是哪一类设备句柄。
如果你知道你想要打开的WAVE OUT设备的ID(但是还未通过waveOutOpen()获取它的句柄),你可以传递这个ID并在mixerOpen的最后一个参数中指定MIXER_OBJECTF_WAVEOUT。mixerOpen()将会找到与这个ID关联的混音器。
你可以通过混音器的句柄获取它的ID,按如下方式使用mixerGetID():
unsigned long
mixerID;
err =
mixerGetID(mixerHandle, &mixerID, MIXER_OBJECTF_HMIXER);
if (err)
{
printf("ERROR: Can't get Mixer Device ID! -- %08X\n", err);
}
else
{
printf("Mixer Device ID = %d\n", mixerID);
}
列举所有的混音器设备
如果你想写一个能列举系统中所有混音器的程序,windows有一个函数可以确定列表中有多少个混音器,这个函数就是mixerGetNumDevs()。它返回系统中混音器的个数。切记设备ID是从0开始并以1递增。因此,如果windows显示列表中有3个混音器,那么你就应该知道它们的ID分别是0、1、2。然后你可以将这些ID用于其他windows函数中。例如,这里有一个函数可以用来获取列表中的设备信息,包括它的名称和其它特征——比如它包含多少个组件及每个组件的类型,等等。你可以传递你想获取信息的混音器设备ID(和一个指向类型为MIXERCAPS的结构体的指针,windows将设备信息填入此结构体),然后调用获取混音器设备信息的函数mixerGetDevCaps()。
如下样例程序遍历混音器列表,并打印出每个设备名:
MIXERCAPS mixcaps;
unsigned long
iNumDevs, i;
/* 获取系统中混音器设备个数 */
iNumDevs =
mixerGetNumDevs();
/* 遍历所有的混音器并显示它们的ID和名称 */
for (i = 0; i <
iNumDevs; i++)
{
/* 获取下一个混音器设备的信息 */
if (!mixerGetDevCaps(i, &mixcaps, sizeof(MIXERCAPS)))
{
/* 显示ID和名称 */
printf("Device ID #%u: %s\r\n", i,
mixcaps.szPname);
}
}
线路和控件
我已经使用了术语“组件”来描述一个具有独立并可调整参数的硬件模块,Mixer API实际上通过信号流来工作。(在我们的方块图中,信号流是那5个连接各个组件的箭头)微软官方文档将每个信号流(就是方块图中的每个箭头)称为一个“源线路”,因此,一个混音器设备控制着“源线路”,而不是组件本身。每个“源线路”有它独立的、可调整的参数——而不是每个组件本身拥有。我们的示例声卡关联的混音器有5个源线路,从此,只要你看到“源线路”,就将它想象为组件之间的信号流。
例如,实际上不是Microphone
Input组件与Mixer API相关联,而是Microphone Input组件和ADC组件之间的信号流与Mixer API关联。Microphone Input音量控件调整着Microphone Input组件和ADC组件间的信号流的音量。
为了更进一步说明“组件”和“源线路”之间的差别,我们再添加一个功能到我们的样例声卡中。有时,你或许希望Microphone
Input组件输入的信号不仅仅能够输送到ADC(因此你可以录音),还可以输送到Speaker Out组件中(这样你就可以监视你正在录制的信号,通过Speaker Out组件能够听到它是个什么样子),因此,我们添加一个方块图来展示信号从Microphone Input组件同时输送到ADC组件和Speaker
Out组件。
alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1036"> |
alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1037"> |
alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1038"> |
alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1039"> |
|
注意我们现在已经有了6条源线路。有两个源线路引出自Microphone Input组件,即使它只是我们声卡上的一个组件。每个源线路都有它自己的设置。例如,
在Microphone
Input和ADC间的源线路上有一个音量控件(这样你就可以设置录音音量)。在Microphone
Input和Speaker Out间的源线路上还有一个单独的音量控件(这样你就可以设置Microphone Input组件的监控音量,与录音音量分离开来)。这些源线路还可以有各自的静音开关(因此你可以让Microphone
Input的信号仅仅输送到ADC,而不输送到Speaker Out)。它们还可以拥有其它的控件,并且每个源线路的控件都和其它线路的控件相分离。
这里还要讨论另外一个和线路相关的重要概念。这里有一些名为“目标线路”的东西,我们要区分源线路和目标线路。它们是些什么东西?哦,一个目标线路有其它的线路——源线路——流向它。在我们的方块图中,