为了理解混音器API是如何工作的,必须先了解典型的声卡的硬件布局。
我们有必要想象一下,声卡上有各种独立的,清楚的,但是却又互相连接的元件。
先看一种典型的,最基本的声卡。首先,如果声卡有数字音频录音功能,它必定有一个麦克风输入接口(以及某种前置放大器(pre-amp)),还有一个模数转换器(ADC)用来转换麦克风的模拟信号为数字信号流。
所以,声卡应该具有两个基本
元件:麦克风输入
元件
,ADC元件。
麦克风输入是和ADC管道联通的(麦克风输入的结果是ADC的输入)。
我们可以用下面的流程图来展示这两个组件以及它们之间的信息流向。
一个典型的声卡也应该有数字音频的播放能力,所以它必定还有一个DAC元件用于把数字信号流转换回模拟信号,它应该还有一个扬声器输出接口(比如还带某种模拟放大器(
analog amplifier))。至此,声卡又多了两种元件:DAC元件,以及扬声器元件。DAC元件和扬声器也是管道联通的。
一个典型的声卡应该还有其他的一些元件。例如,它或许有内置的某种声音模块(比如合成器)用于播放MIDI数据。这个元件的音频输出和DAC的输出一样,会被管道输出到扬声器。我们的数据流图现在看上去是这样:
另外,一个典型的声卡内部有一个连接器挂接到电脑光驱的音频输出上(这样一张音频CD在光驱中播放的话会通过扬声器发声)。这个元件也是管道输出到扬声器的,如同合成器还有DAC元件一样。现在,我们的数据流图是这样:
最后,我们假定声卡拥有一个线路输入(LineIn)的元件,以使从外部的磁带机和乐器或者外部硬件混音器输出的音频信号能被挂接到这个接口上并被数字化。这个元件管道输出到ADC元件,同麦克风输入一样。至此,我们完成了拥有7个元件的声卡图(5个信号流,箭头表示它们之间的连接):
一般来说,以上的每个元件都有它自己独特的参数设置。例如,合成器通常有自己的音量。外部的CD音频也有它自己的音量。于是,如果用户同时播放一直音乐CD,播放一个MIDI文件,回放一个WAVE文件,它们共同输出到扬声器元件,那么他可以在这三个元件的音量之间做调节,并且,扬声器本身也有它自己的音量调节,这个主音量会影响其他三个元件管道输出到扬声器的混合音频。
同样,线路输入和麦克风输入通常有独立的音量,所以它们在同时通过各自的接口进行录音的时候能进行调节。ADC元件也可能会有某种主音量控制器来影响这两个元件的管道输入。
一个给定的元件可能会有其他的独立可调整参数。例如,以上每个元件都有它自己的静音开关,以便于快速地开关各个元件的声音。
混音器设备
每块给定的声卡有和它关联的混音器设备。声卡上所有的不同种类的元件都会通过该声卡的混音器设备来进行操控。Windows的混音器API用于访问混音器设备。混音器API有函数可以取得指定声卡上所有元件的列表,并且调节它们各自的参数。这个是新加入到Win95、Win98和WINNT的API,不过也可以通过一个提供给Windows3.1的扩展用到其他的老版本操作系统上。
注意:声卡驱动需要额外的支持才能和混音器API很好的兼容。所以不是所有的Win95和WinNT驱动都能得到支持。Win3.1的驱动就是典型的不支持的例子。
在任何电脑上,都可以安装超过一张的声卡。你也许已经发现,windows在系统中维护了一份所有WAVE和MIDI输入输出设备的列表。由于每张声卡都对应它自己的混音器设备(只要其驱动支持),所以windows也在系统中维护了一份已安装的混音器设备的列表。例如,如果你在一个系统上安装了两张声卡,那么也会有两个混音器设备被安装(假设每张声卡的驱动都支持混音器API的话)。如同WAVE和MIDI输入输出设备,windows给每个混音器设备也分配了一个数字ID。所以,ID为0的混音器设备是系统的第一个混音器(默认是)。如果有第二张声卡,那么就会有ID为1的混音器设备存在。
类似windows的其他设备,为使用某个混音器,你首先必须得打开它(mixerOpen()),然后你可以调用混音器API来控制声卡的线路输入输出。做完这些之后,你必须关闭它(mixerClose())。
打开一个混音器设备
你的程序如何选中某个混音器设备来进行操作呢?你可以有好几种不同的方法,取决于你想你的程序有多特别,以及扩展性有多好。
如果你只想简单地打开一个默认的混音器,那么可以用mixerOpen()打开ID为0的混音器设备,如下:
unsigned long err;
HMIXER mixerHandle;
err = mixerOpen(&mixerHandle, 0, 0, 0, 0);
if (err)
{
printf("ERROR: Can't open Mixer Device! -- %08X\n", err);
}
else
{
}
当然,如果用户没有安装任何的混音器设备,那么上面的这个调用会返回一个错误。所以,任何时候都要记得检查返回值(混音器API返回的可能的错误号都列举在MMSYSTEM.H文件里。不幸的是,不想WAVE和MIDI的底层API那样,没有一个API能够把这些错误号翻译为直观的字符串)。
那么,首选的混音器设备究竟是什么呢?那就是第一个被安装在系统上的——无论什么混音器设备。如果系统只有一块声卡,那么你可以很确信你有一个需要的混音器设备了。但是,假如你想尝试使用第二块声卡上的WAVE输出呢?你绝不会想用第一块声卡的混音器设备去控制第二块声卡的WAVE输出音量的(第一块声卡的混音器不可能控制第二块声卡的WAVE输出)。
那么,你怎么去打开想要的那个声卡的混音器呢?幸运的是,mixerOpen()允许你传递一个设备ID号给它,或者是与你感兴趣的声卡的其他设备关联的句柄给它。这样的话,mixerOpen()会确保它返回那块声卡上与该设备相关联的
一个
混音器设备。举个例子,下面展示你如何去打开默认的WAVE输出设备(默认声卡的WAVE输出),然后取得这个声卡的混音器设备句柄:
unsigned long err;
HMIXER mixerHandle;
WAVEFORMATEX waveFormat;
HWAVEOUT hWaveOut;
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
{
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) ,以确保此种的设备句柄传递给了mixerOpen()。
另外,如果你已经知道了想要操纵的 WAVE OUT的设备ID号(但是你没有用waveOutOpen()去打开它),你也可以把这个ID号传递给mixerOpen() ,而不必指定MIXER_OBJECTF_WAVEOUT。mixerOpen()会找到与该指定的ID相关联的WAVE输出设备对应的混音器。
如果有必要,你可以用上面的混音器的句柄取得它的的混音器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开始增长的。所以如果windows说列表里有3个设备,那么你应该知道设备ID分别是0,1,2。你现在可以在其他函数中使用这些设备ID了。例如,有个函数可以取得列表里某个设备的信息,它的名字就代表了它的功能。这些信息包括它有多少元件,每个元件分别是什么类型。你可以将你想去的信息的混音器的设备ID传给它(还需要一个特殊的结构体MIXERCAPS的指针 ,windows把关于此设备的信息都放在其中)。这个取得设备信息的函数名字叫做mixerGetDevCaps()。这里是一个取得混音器列表的例子,它打印出每个混音器的名字:
MIXERCAPS mixcaps;
unsigned long iNumDevs, i;
iNumDevs = mixerGetNumDevs();
for (i = 0; i < iNumDevs; i++)
{
if (!mixerGetDevCaps(i, &mixcaps, sizeof(MIXERCAPS)))
{
printf("Device ID #%u: %s\r\n", i, mixcaps.szPname);
}
}
关于线路和控制器
在此之前,我用“元件”来指代一种有自己独立可调整的参数的硬件区域。实际上,混音器API本身操控的是“信号流”(在我们的块状图里,信号流就是连接几个元件的5个箭头)。微软的文档指出,每个信号流(即对应于图里的每个箭头)对应于一条源线路。因此,一个混音器设备控制着“源线路”——而不是元件。每条源线路——而不是元件有着它们自己独立可调整的参数。我们的样例声卡拥有一个混音器关注的5条源线路。此后,无论什么时候看到“源线路”,就想想在元件之间的信号流。
之前,我也用到了“参数”一词来指代每条线路上可调整的设置。例如一个音量调节器或者一个静音开关,或者在设置面板上的重低音(bass boast)选项。微软的文档称这些参数为“控制器”(Control这是个容易让人感到困惑的词汇,一般程序员会将其想象成某图形界面的窗体上的一个控件。但是对于混音器来说,它的意思是“音频控制”)。
举个例子,混音器API关注的不是真的“麦克风输入”元件,而是在麦克风输入和ADC元件之间的信号流。“麦克风输入”的音量控制器调整的是在麦克风输入和ADC元件之间的信号流。
为了更进一步的说明元件和源线路之间的区别,让我们为我们的样例声卡添加一个新功能。有时候,你会想让麦克风输入不仅仅是管道输入到ADC(这样你能对其进行录音),而且还要管道输出到扬声器(这样你就可以监测出你实际在录制的是什么信号,而且可以通过扬声器听到它的声音)。所以,我们调整一下我们的区块图,可以看到,从麦克风输入出来的信号,送到了ADC,也送到了扬声器。
注意,我们现在有了6条源线路(箭头)。有两条源线路从麦克风输入出来,尽管它只是我们声卡上的单独一个元件。每条源线路都有它自己的设置。例如,麦克风输入到ADC的这条源线路有它的音量控制器(这样你可以设置你的WAVE录音软件的录音范围)。而且麦克风输入到扬声器输出的这条线路也有它的音量控制器(这样你可以独立于录音范围,设置你的麦克风的检测范围)。这些源线路也许每个都有它们自己的静音开关(这样你就可以让麦克风输入只送到ADC,而不送到扬声器)。它们当然也有其他的控制器,每条线路的控制器都独立于另外的线路存在。
关于线路,还有另外一个重要的话题需要探讨。有种东西叫做“目标线路”,用以区分“源线路”。这些线路是什么呢?一条目标线路指的是有其他源线路指向它的线路。在我们的区块图里,扬声器输出就是一条目标线路,它有来自于内部CD音频,合成器,DAC WAVE输出和麦克风输入的信号来源指向它,而后面4个就是源线路。源线路总是会指向某个目标线路(即,不可能存在不关联于任何一条目标线路的源线路)。
所以,鉴于源线路指的是我们区块图中的箭头,那么目标线路其实指的就是我们图中实际的那些元件。
因此,ADC WAVE输入是我们样例声卡的一条目标线路。它有两条源线路指向它——麦克风输入和线路输入。这两条线路不是扬声器输出的源线路,因为它们并未指向它。类似地,扬声器输出的4条源线路不是ADC WAVE输入的源线路。
同于源线路,目标线路也有自己的控制器。每个目标线路的控制器也和其他线路的控制器是独立的。例如,扬声器输出可能有一个音量调节器,这可以成为它的4条源线路的主音量调节器(这4条源线路也有自己的音量控制器)。
注意:尽管一块声卡可能有立体声元件(例如,对大多数声卡来说扬声器输出通常就是一个立体声元件),这也被看做是一条线路。它有两个声道,然而它不过是混音器的一条线路。甚至,一个元件可能有着多余两个的声道,但是仍然被当作是一条线路。总体来说,线路总是不同于声道的存在。某些情况下,可能把线路当作一条MIDI连线会比较有用。一条MIDI连线连接两个元件,但是这个连线内部可能有多个声道贯穿其中。这和我们说的“线路”是一样的。
总之,我们的样例声卡有6条源线路和两条目标线路。4条源线路分别标记为内部CD音频,合成器,DAC WAVE输出,和麦克风输入,它们连接到扬声器输出这个目标线路上。另外两条标记为麦克风输入,线路输入的源线路连接到ADC WAVE输入这个目标线路上。
线路ID和类型
每条线路必须有一个唯一的ID数字。每个线路也具有一个类型。这只是一个描述具体它是哪种类型的数值。它们定义在MMSYSTEM.H里面。例如,一个MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER类型的线路指的是它是来自内置声音模块的信号。
源线路的所有可能的类型如下:
1)MIXERLINE_COMPONENTTYPE_SRC_DIGITAL
数字信号源,例如SPDIF输入接口.
2)MIXERLINE_COMPONENTTYPE_SRC_LINE
线路输入源。如果存在独立的麦克风输入的话,通常用于线路输入接口(即MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE)
3)MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE
麦克风输入(但如果不存在独立的线路输入源的话,经常和和Mic/Line输入联合起来使用)
4)MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER
音乐合成器。通常用于带有一个能播放MIDI的合成器的声卡。这将是内置合成器的音频输出。
5)MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC
此音频信号来自内部CDROM光驱(连接至声卡)
6)MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE
一般用于通过电脑的扬声器被管道输入进来的电话线路的输入音频,或者内置modem的电话线路接口。
7)MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER
通常,为了发声,音频是送到电脑的内置扬声器,而不是被送到声卡的扬声器输出。为了达到这样的目的,主板的系统扬声器连接器会被内部连接到声卡的某个连接器上。
8)MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT
WAVE重放(此即声卡的DAC)。
9)MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY
AUX接口意味着音频会被送到扬声器输出,或者ADC(如果是WAVE录音的话)。一般这被用来连接外部的模拟信号设备(如磁带机,乐器音频输出等)用来做音频数字化或者通过声卡作回放。
10)MIXERLINE_COMPONENTTYPE_SRC_ANALOG
同MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY用途近似(尽管我曾见过一些混音器使用MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER)。一般这会是声卡上的某种只能被内部访问的模拟信号连接器,用来内部连接一些电脑内部的模拟信号元件,以通过扬声器发声。
11)MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED
未定义的源类型。以上所列都不合适。
目标线路的所有可能的类型如下:
1)MIXERLINE_COMPONENTTYPE_DST_DIGITAL
数字信号目标,例如SPDIF输出接口。
2)MIXERLINE_COMPONENTTYPE_DST_LINE
线路输出目标。如果存在独立的扬声器输出的话,通常用于线路输出接口(即MIXERLINE_COMPONENTTYPE_DST_SPEAKERS)。
3)MIXERLINE_COMPONENTTYPE_DST_MONITOR
通常一个监视器输出接口用来作为独立于主要扬声器输出之外的扬声器系统。或者,也可能是声卡上本身的一些内置监视器扬声器,如内置modem上的扬声器。
4)MIXERLINE_COMPONENTTYPE_DST_SPEAKERS
输出到一对扬声器(即扬声器输出接口)的音频。
5)MIXERLINE_COMPONENTTYPE_DST_HEADPHONES
通常是头戴式耳机的输出接口。
6)MIXERLINE_COMPONENTTYPE_DST_TELEPHONE
通常用作串行连接(daisy-chain)电话到模拟调制解调器(analog modem)的电话输出接口上。
7)MIXERLINE_COMPONENTTYPE_DST_WAVEIN
声卡的ADC(用来数字化模拟信号源,例如录制WAVE文件)
8)MIXERLINE_COMPONENTTYPE_DST_VOICEIN
或许是某种用来做声音识别的硬件。通常麦克风源线路会被连接到这上面。
9)MIXERLINE_COMPONENTTYPE_DST_UNDEFINED
未定义的目标类型。以上所列都不合适。
控制器ID和类型
每条线路可以有一个或者多个可调节的音频控制器(也可能没有任何一个控制器)。例如,合成器线路会有一个音量调节器和一个静音开关。每个控制器都有一个类型。它们定义在MMSYSTEM.H里面。例如,音量减淡器具有类型MIXERCONTROL_CONTROLTYPE_VOLUME。静音开关具有类型MIXERCONTROL_CONTROLTYPE_MUTE。
每个控制器都有个唯一的ID。没有具有同一个ID号的两个控制器存在,即使它们分属不同线路。控制器类型划分为一些类。这些类粗略地表示了一个控制器调节的是什么类型的值,也表示了通常情况下你作为一个终端用户去调节这个值的图形界面种类。例如,通常你会提供一个图形化的音量调节器来允许你的用户调节MIXERCONTROL_CONTROLTYPE_VOLUME类型的控制器。另一方面,你通常会用一个带选中标记的按钮来让用户调节MIXERCONTROL_CONTROLTYPE_MUTE 控制器(因为这种控制器只有两个可能的值)。
控制器允许的类型如下所示:
1)MIXERCONTROL_CT_CLASS_FADER
这是通过竖向的调节器(vertical fader)来调节的控制器,它带线性的正值刻度(0是最小的可选值)。
MIXERCONTROLDETAILS_UNSIGNED 结构体用来获取或者设置该控制器的值。
2)MIXERCONTROL_CT_CLASS_LIST
这是通过提供多个值供选择的列表框来调节的控制器。用户可以单选,或者多选其中的值。
MIXERCONTROLDETAILS_BOOLEAN 结构体用来获取或者设置该控制器的值。
也可以用MIXERCONTROLDETAILS_LISTTEXT 结构体来获取该控制器的值的每个元素的描述文本。
3)MIXERCONTROL_CT_CLASS_METER
这是通过图形化仪表(graphical meter)来调节的控制器。
使用MIXERCONTROLDETAILS_BOOLEAN,或者MIXERCONTROLDETAILS_SIGNED,或者MIXERCONTROLDETAILS_UNSIGNED 结构体来获取或者设置该控制器的值。
4)MIXERCONTROL_CT_CLASS_NUMBER
这是通过数字输入框( numeric entry)来调节的控制器。用户可以输入有符号整数,无符号整数,或者普通整数的分贝值。
5)MIXERCONTROL_CT_CLASS_SLIDER
这是通过水平滑动的推子(fader)来调节的控制器,它带线性的正负值刻度(通常0是中间点或者中性值)。
MIXERCONTROLDETAILS_SIGNED 结构体用来获取或者设置该控制器的值。
6)MIXERCONTROL_CT_CLASS_SWITCH
这是只有两个状态(值)可供选择的控制器,所以通过一个按钮来调节。
MIXERCONTROLDETAILS_BOOLEAN 结构体用来获取或者设置该控制器的值。
7)MIXERCONTROL_CT_CLASS_TIME
这是允许用户输入一个时间值的控制器。例如混响衰减时间( Reverb Decay Time)。控制器的值是正整数。
8)MIXERCONTROL_CT_CLASS_CUSTOM
用户定义的控制器类。上面都不适合的时候使用。
每个类都关联于某些对应的类型。例如,MIXERCONTROL_CT_CLASS_FADER类关联有下面5个类型:
1)MIXERCONTROL_CONTROLTYPE_VOLUME
音量调节器(Volume fader)。取值范围0-65535。
2)MIXERCONTROL_CONTROLTYPE_BASS
低音增强调节器(Bass boost fader)。取值范围0-65535。
3)MIXERCONTROL_CONTROLTYPE_TREBLE
高音增强调节器(Treble boost fader)。取值范围0-65535。
4)MIXERCONTROL_CONTROLTYPE_EQUALIZER
图形化均衡器(Graphic EQ)。每个栏(band )取值范围0-65535。
MIXERCONTROLDETAILS_LISTTEXT结构体用于查询据衡器的每个通道的文本标签。通常,该控制器也有5个
MIXERCONTROL_CONTROLF_MULTIPLE 比特位标记,因为均衡器可能有多个通道。
5)MIXERCONTROL_CONTROLTYPE_FADER
一般调机器(Generic fader)。以上都不合适的时候使用。取值范围0-65535。
事实上,如果你去看MMSYSTEM.H,你会注意到MIXERCONTROL_CONTROLTYPE_VOLUME类型的控制器是被定义为MIXERCONTROL_CT_CLASS_FADER | MIXERCONTROL_CT_UNITS_UNSIGNED + 1。这个类其实包含在类型数字的高4比特上。所以如果你知道了一个控制器类型,你可以去掉它的高4比特的值来得到它对应的类。 例如,假如你已经查询到一个控制器的类型,然后将它存放在了混音器API返回给你的名叫type的变量里。下面演示你如何来找到它对应的类类型:
unsigned long type;
switch (MIXERCONTROL_CT_CLASS_MASK & type)
{
case MIXERCONTROL_CT_CLASS_FADER:
{
printf("It's a fader class.");
break;
}
case MIXERCONTROL_CT_CLASS_LIST:
{
printf("It's a list class.");
break;
}
case MIXERCONTROL_CT_CLASS_METER:
{
printf("It's a meter class.");
break;
}
case MIXERCONTROL_CT_CLASS_NUMBER:
{
printf("It's a number class.");
break;
}
case MIXERCONTROL_CT_CLASS_SLIDER:
{
printf("It's a slider class.");
break;
}
case MIXERCONTROL_CT_CLASS_TIME:
{
printf("It's a time class.");
break;
}
case MIXERCONTROL_CT_CLASS_CUSTOM:
{
printf("It's a custom class.");
break;
}
}
MIXERCONTROL_CT_CLASS_SWITCH类有7种关联类型:
1)MIXERCONTROL_CONTROLTYPE_BOOLEAN
值为bool型的控制器。值是整数,要么为0(FALSE),要么非0(TRUE)。
2)MIXERCONTROL_CONTROLTYPE_BUTTON
按钮被按下的时候值为1(某功能或者动作被启用),没被按下的时候值为0
(不采取行动)
的控制器。
例如,这个类型的控制器可以被用来作为对讲按钮或者延音踏板(
pedal sustain)——仅当按钮按下的时候行动/功能才是打开的,相反则是禁用的。
3)MIXERCONTROL_CONTROLTYPE_LOUDNESS
值为1的时候打开超重低音(增加低音频率
boost bass frequencies
),为0时候是正常状态(不增强低音)。
在该控制器打开超重低音的时候,
MIXERCONTROL_CONTROLTYPE_BASS 增减益控制器可以用来设置具体的增强值。
4)MIXERCONTROL_CONTROLTYPE_MONO
值为1的时候用来进行单声道操作(所有声道被合并为一个),为0时候是正常状态(立体声或者多声道)。
5)MIXERCONTROL_CONTROLTYPE_MUTE
值为1的时候用来对某功能静音,为0时候是正常状态(不静音)。
6)MIXERCONTROL_CONTROLTYPE_ONOFF
值为1的时候启用某功能或行为,为0时候禁用此
功能或行为
。
和
MIXERCONTROL_CONTROLTYPE_BUTTON不同的是,后者的0值不会
禁用此
功能或行为本身,而只是简单的表现出
此
功能或行为不应用的状态。
MIXERCONTROL_CONTROLTYPE_ONOFF和真实的开关更类似(按windows的说法就是带选中标记的按钮),然而
MIXERCONTROL_CONTROLTYPE_BUTTON 更类似于一个暂时的开关(按压式按钮)。
MIXERCONTROL_CONTROLTYPE_ONOFF 和
MIXERCONTROL_CONTROLTYPE_BOOLEAN 的不同仅仅是在用户界面表现上的标记或者图像不同。值为1时前者表示为ON而后者表示为TRUE。所以通常,这两个按钮在图形界面上展示为不同的标签,以此反应语义上的不同。
7)MIXERCONTROL_CONTROLTYPE_STEREOENH
值为1的时候启用立体声增强功能(增强立体声的分割),为0时候是正常状态(不增强)。
MIXERCONTROL_CT_CLASS_LIST
类有4种关联类型:
1)MIXERCONTROL_CONTROLTYPE_SINGLESELECT
允许从众多的可选项里面选择一个值。例如,可以用来从很多类型(
Hall,Plate,Room等
)里面选择一个混响类型。
2)MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT
类似MIXERCONTROL_CONTROLTYPE_SINGLESELEC,但是允许同时选中多个的值。
3)MIXERCONTROL_CONTROLTYPE_MUX
允许从一些音频线路中选择一条音频线路。例如,为了让一条源线路被连接到多个可能的目标线路中的一条——该控制器应该列出所有可能的目标线路,允许其中一条被选中。
4)MIXERCONTROL_CONTROLTYPE_MIXER
类似MIXERCONTROL_CONTROLTYPE_MUX,但是允许同时选中多于一条的线路。例如,这个控制器可以用于一个混响元件,允许一些源线路同时连接到它,该控制器决定哪些源线路被选中用于连接到混响元件。
MIXERCONTROL_CT_CLASS_METER类有4种关联类型:
1)MIXERCONTROL_CONTROLTYPE_BOOLEANMETER
表计,整数值为0(
FALSE
)或者非0(TRUE)。采用 MIXERCONTROLDETAILS_BOOLEAN 结构体获取或者设置此值。
2)MIXERCONTROL_CONTROLTYPE_PEAKMETER
一个整数值的控制器,最大范围从-32,768(最低)到32,767(最高)。也就是说,该值为SHORT类型。MIXERCONTROLDETAILS_SIGNED 结构体用来获取或设置它的值。
3)MIXERCONTROL_CONTROLTYPE_SIGNEDMETER
一个整数值的控制器,最大范围从
-2,147,483,648
(最低)到
2,147,483,647
(最高)(包括)
。也就是说,该值为
ULONG
类型。
MIXERCONTROLDETAILS_SIGNED
结构体用来获取或设置它的值。
4)MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER
类似MIXERCONTROL_CONTROLTYPE_SIGNEDMETER。但
最大范围从
0
(最低)到
4,294,967,295
。也就是说,该值为
ULONG
类型。
MIXERCONTROLDETAILS_UNSIGNED
结构体用来获取或设置它的值。
其他
MIXERCONTROL_CT_CLASS_NUMBER 类
允许的类型类似于MIXERCONTROL_CT_CLASS_METER类。但是对于MIXERCONTROL_CT_CLASS_NUMBER类,你通常会用一个Edit控件放置在图形界面上以供用户输入某个值。而对于 MIXERCONTROL_CT_CLASS_METER 类,通常会用某种近似于音频表计的图形来做展示。
MIXERCONTROL_CT_CLASS_NUMBER类有4种关联类型:
1)MIXERCONTROL_CONTROLTYPE_SIGNED
一个整数值的
控制器
,最大范围从
-2,147,483,648
(最低)到
2,147,483,647
(最高)(包括)
。也就是说,该值为
LONG
类型。
MIXERCONTROLDETAILS_SIGNED
结构体用来获取或设置它的值。
2)MIXERCONTROL_CONTROLTYPE_UNSIGNED
类似MIXERCONTROL_CONTROLTYPE_SIGNEDMETER。但
最大范围从0(最低)到
4,294,967,295
。也就是说,该值为
ULONG
类型。
MIXERCONTROLDETAILS_UNSIGNED
结构体用来获取或设置它的值。
3)MIXERCONTROL_CONTROLTYPE_PERCENT
该控制器的整数值代表百分数。
MIXERCONTROLDETAILS_UNSIGNED
结构体用来获取或设置它的值。
4)MIXERCONTROL_CONTROLTYPE_DECIBELS
一个整数值的
控制器
,最大范围从-32,768(最低)到32,767
(最高)
。也就是说,该值为SHORT类型。增长步长是10分贝的倍数。
MIXERCONTROLDETAILS_SIGNED
结构体用来获取或设置它的值。
MIXERCONTROL_CT_CLASS_SLIDER类有3种关联类型:
1)MIXERCONTROL_CONTROLTYPE_SLIDER
一个带整数值的滑动条,最大范围从-32,768(最低)到32,767
(最高)
。也就是说,该值为SHORT类型。
2)MIXERCONTROL_CONTROLTYPE_PAN
一个带整数值的滑动条,最大范围从-32,768
(最左边)
到32,767
(最右边)
。也就是说,该值为SHORT类型。代表立体声频谱中的左右声道均衡(pan)位置,0为最中间的值。
3)MIXERCONTROL_CONTROLTYPE_QSOUNDPAN
一个带整数值的滑动条,最大范围从-15(最低)到15(最高)。也就是说,该值为SHORT类型。代表
Qsound的扩展声音设置。
MIXERCONTROL_CT_CLASS_TIME类有两种关联类型:
1)MIXERCONTROL_CONTROLTYPE_MICROTIME
一个带整数值的控制器,
最大范围从0(最低)到
4,294,967,295
。
也就是说,该值为
ULONG
类型。代表微秒数的时间。
2)MIXERCONTROL_CONTROLTYPE_MILLITIME
一个带整数值的控制器,最大范围从0(最低)到4,294,967,295。也就是说,该值为ULONG类型。代表毫秒数的时间。
MIXERCONTROL_CT_CLASS_CUSTOM类是一个专属类。一个使用这个类型的控制器的混音器只期望一个专门为此混音器编写的程序会理解这个类里面究竟是什么类型的控制器,以及该用什么样的结构体去获取或者是设置该控制器的值(或许也只有专属的结构体可以拿来使用)。
MIXERLINE结构体,以及枚举所有线路
一个取得混音器线路信息的方法是,如果你不知道它具体有哪些线路(即你不知道线路的具体类型或者它们的ID),应该先调用mixerGetDevCaps()来将混音器设备的信息取到一个MIXERCAPS结构体中。通过这个结构体中的信息,你能得知声卡上有几个目标线路。这样,你可以枚举每条目标线路(即取得信息),以及和它们关联的各自的源线路。在枚举完线路之后,你可以枚举每条线路上的控制器。
让我们来验证这样的做法,并学习和混音器API相关的这些结构体。
为了更好的理解混音器API,我们应该对我们样例声卡的内部进行一个概览,查看我们使用混音器API时候的内部结构体。我们假设混音器设备是用C语言写成,这里使用C的结构体进行描述。
就像之前提到的那样,混音器API mixerGetDevCaps()用来取得一个混音器设备的信息。信息被填充到MIXERCAPS 结构体中。特别地,cDestinations 字段可以告诉你声卡上有多少条目标线路。它不会告诉你总的有多少条线路(即目标线路加上源线路),它只计算目标线路。我们的样例声卡只有两条目标线路。由于是例子,我们用任意值来填充其他字段,例如混音器名字和产品ID。假设这个混音器是系统安装的第一个混音器(即ID为0)。那么这就是我们的混音器设备的MIXERCAPS结构体(定义在MMSYSTEM.H内):
MIXERCAPS mixercaps = {
0,
0,
0x0100,
"Example Sound Card",
0,
2,
};
下面是个将MIXERCAPS传递给mixerGetDevCaps()以使windows帮助我们填充以上所有字段的例子(假设我们已经打开了混音器并且已经将其句柄存放到变量mixerHandle中):
MIXERCAPS mixcaps;
MMRESULT err;
if (!(err = mixerGetDevCaps((UINT)mixerHandle, &mixcaps, sizeof(MIXERCAPS))))
{
}
else
{
printf("Error #%d calling mixerGetDevCaps()\n", err);
}
一条线路的信息被存放在MIXERLINE结构体(也定义在MMSYSTEM.H)内。我们假设我们的扬声器输出目标线路有两个控制器:一个滑动音量调节器(用来控制输出到扬声器的混音音量)和一个静音开关(用来对所有混音静音)。下面是用于扬声器输出目标线路的MIXERLINE结构体的例子:
MIXERLINE mixerline_SpkrOut = {
sizeof(MIXERLINE),
0,
0,
0xFFFF0000,
MIXERLINE_LINEF_ACTIVE,
0,
MIXERLINE_COMPONENTTYPE_DST_SPEAKERS,
2,
4,
2,
"Spkr Out",
"Speaker Out",
MIXERLINE_TARGETTYPE_WAVEOUT,
0,
0,
0,
0x0100,
"Example Sound Card",
};
这里有一些需要注意的地方。首先,扬声器输出目标线路的dwComponentType是目标线路类型——MIXERLINE_COMPONENTTYPE_DST_SPEAKERS。这个类型值恰当的表明这是一个
扬声器输出
。我选择了一个值0xFFFF000赋给dwLineID字段。混音器设备开发者可以为其赋予任何想要的值给这个字段,但是混音器内其它任何线路的dwLineID字段的值不能和此值相同(接下来你就会注意到这点)。同样需要注意的是,既然我们的
扬声器输出
是立体声的,因此cChannels的值就是2。你应该还记得,有4个源线路关联到我们的
扬声器输出
目标线路,因此cConnections字段的值是4。Name字段是以null结尾的字符串,它可以是开发者选择的任意值,但短一些的名字意味着它可以用来作为图形界面控件上的狭窄空间的标签显示。当fdwLine字段中设置有MIXERLINE_LINE_ACTIVE标志,这意味着此线路没有被禁用(就好像静音的时候会发生的事)。
dwDestination字段是一个从0开始的索引值,混音器中的第一个目标线路的索引值为0(如上面示例)。第二个目标线路的索引值是1,第三个目标线路的索引值为2,以此类推。这和windows枚举混音器设备的概念一样(即第一个安装的混音器的ID将为0)。索引值和线路的ID并不一定相同(你可以从上面的示例中看出),那么为什么使用索引值呢?为什么要同时使用索引值和ID号呢?或许你可以猜到,索引值主要在你需要枚举一个混音器有哪些线路时使用。直到你枚举了所有的线路(即获取每个线路的信息)之后,你才能知道每个线路的ID。因此,当你需要使用混音器API去枚举线路的时候会用到这些索引值。但是一旦你已经取得了某个线路的信息,就知道了它的ID,然后你就可以通过ID更直接的更改它的设置。因此,索引值在起初枚举线路和控制器以获取其ID及类型时很有用。在随后的操纵线路和控制的过程中ID就起主要作用。
现在我们来看一下目标线路ADC WAVE输入的MIXERLINE结构。我们假设它有两个控制器——一个滑动音量
调节器
和一个静音开关。这是目标线路ADC WAVE 输入的MIXERLINE结构的一个例子:
MIXERLINE mixerline_WaveIn = {
sizeof(MIXERLINE),
1,
0,
0xFFFF0001,
MIXERLINE_LINEF_ACTIVE,
0,
MIXERLINE_COMPONENTTYPE_DST_WAVEIN,
2,
2,
2,
"Wave In",
"Wave Input",
MIXERLINE_TARGETTYPE_WAVEIN,
0, 0, 0, 0x0100, "Example Sound Card",
};
注意目标线路ADC WAVE输入的字段dwComponentType是MIXERLINE_COMPONENTTYPE_DST_WAVEIN类型,这表明它是一个波形输入。同样,我选择了值0xFFFF0001赋给dwLineID —— 不同于和扬声器输出目标线路的dwLineID的值。同时,注意我们的声卡还具有数字化到立体声的功能,因此cChannels字段值是2。你应该还记得,有2条源线路连接到我们的目标线路ADC WAVE输入,因此cConnections的值是2。最后,注意dwDestination字段的值是1,因为这是混音器中第二个目标线路。
混音器API mixerGetLineInfo()为指定的线路填充MIXERLINE结构。这就是你获取某个线路信息的方法。如果你不知道一条线路的ID(在你第一次枚举线路的情况下),那么你可以通过索引值来引用它。通过给mixerGetLineInfo()传递值MIXER_GETLINEINFOF_DESTINATION来表明你想通过线路的索引值来引用线路。例如,下面是如何获取我们的样例混音器中第一条目标线路的信息。在调用mixerGetLineInfo()和传递MIXERLINE结构体去填充之前,你必须初始化两个字段。cbStruct字段必须设置为你传递的MIXERLINE结构所占的字节数,dwDestination必须设置为你想获取信息的线路的索引值。记住第一条目标线路的索引值为0,因此如果要获取它的信息,我们将dwDestination设置为0。
MIXERLINE mixerline;
MMRESULT err;
mixerline.cbStruct = sizeof(MIXERLINE);
mixerline.dwDestination = 0;
if ((err = mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerline, MIXER_GETLINEINFOF_DESTINATION)))
{
printf("Error #%d calling mixerGetLineInfo()\n", err);
}
当上面的调用返回后, mixerGetLineInfo() 会根据扬声器输出(mixerline_SpkrOut)的MIXERLINE结构体填充我们的MIXERLINE结构体(毕竟,扬声器输出是我们样例混音器的第一条线路,它索引值为0)。
现在,如果你想取得混音器的第二条目标线路的信息,唯一不同的就是你设置给dwDestination字段的值是第二个目标线路的索引(1),如下:
mixerLine.cbStruct = sizeof(MIXERLINE);
mixerLine.dwDestination = 1;
if ((err = mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerLine, MIXER_GETLINEINFOF_DESTINATION)))
{
printf("Error #%d calling mixerGetLineInfo()\n", err);
}
当上面的调用返回后, mixerGetLineInfo() 会根据ADC WAVE输入(
mixerline_WaveIn
)的MIXERLINE结构体填充我们的MIXERLINE结构体(毕竟,
ADC WAVE输入
是我们样例混音器的第二个线路,它索引值为1)。
现在,你应该明白了如何根据索引值去枚举目标线路。下面是个示例,它打印出混音器中所有目标线路的名字:
MIXERCAPS mixcaps;
MIXERLINE mixerline;
MMRESULT err;
unsigned long i;
if (!(err = mixerGetDevCaps((UINT)mixerHandle, &mixcaps, sizeof(MIXERCAPS))))
{
for (i = 0; i < mixercaps.cDestinations; i++)
{
mixerline.cbStruct = sizeof(MIXERLINE);
mixerline.dwDestination = i;
if (!(err = mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerline, MIXER_GETLINEINFOF_DESTINATION)))
{
printf("Destination #%lu = %s\n", i, mixerline.szName);
}
}
}
现在,我们需要枚举每条目标线路的所有源线路。我们也用索引值来引用每条源线路。对于一条给定的目标线路来说,第一条源线路索引值为0。该目标线路的第二条源线路索引值为1,第三条源线路索引值为2,以此类推。记住扬声器输出关联有4条源线路:内部CD音频,合成器,DAC WAVE输出,麦克风输入。所以它们各自的索引值为0,1,2,3。我们来看看这4条源线路的
MIXERLINE 结构体。假设每条源线路有两个控制器,一个滑动音量调节器和一个静音开关。另外假设每条源线路都是一个立体声源。
MIXERLINE mixerline_CD = {
sizeof(MIXERLINE),
0,
0,
0x00000000,
MIXERLINE_LINEF_ACTIVE|MIXERLINE_LINEF_SOURCE,
0,
MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC,
2,
0,
2,
"CD Audio",
"Internal CD Audio",
MIXERLINE_TARGETTYPE_UNDEFINED,
0, 0, 0, 0x0100, "Example Sound Card",
};
MIXERLINE mixerline_Synth = {
sizeof(MIXERLINE),
0,
1,
0x00000001,
MIXERLINE_LINEF_ACTIVE|MIXERLINE_LINEF_SOURCE,
0,
MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER,
2,
0,
2,
"Synth",
"Synth",
MIXERLINE_TARGETTYPE_UNDEFINED,
0, 0, 0, 0x0100, "Example Sound Card",
};
MIXERLINE mixerline_WaveOut = {
sizeof(MIXERLINE),
0,
2,
0x00000002,
MIXERLINE_LINEF_ACTIVE|MIXERLINE_LINEF_SOURCE,
0,
MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT,
2,
0,
2,
"Wave Out",
"DAC Wave Out",
MIXERLINE_TARGETTYPE_WAVEOUT,
0, 0, 0, 0x0100, "Example Sound Card",
};
MIXERLINE mixerline_Mic = {
sizeof(MIXERLINE),
0,
3,
0x00000003,
MIXERLINE_LINEF_ACTIVE|MIXERLINE_LINEF_SOURCE,
0,
MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE,
2,
0,
2,
"Mic",
"Microphone Input",
MIXERLINE_TARGETTYPE_WAVEIN,
0, 0, 0, 0x0100, "Example Sound Card",
};
你必须注意的一件事是:不同于目标线路,源线路的MIXERLINE中有它们的MIXERLINE_LINE_SOURCE标志。当这个标志被设置时,你就知道你获取的是一条源线路的信息。其次,注意这个目标线路的索引值是0。这是因为以上所有源线路连接的目标线路扬声器输出是我们的混音器的第一条线路(因此它的索引值为0)。再次,注意以上4个源线路的索引值分别是0、1、2和3。最后,注意每个源线路的ID都是唯一的——
不像
包括任何目标线路的
其它所有线路
。
混音器API mixerGetLineInfo()同样会为源线路填充MIXERLINE结构体。同样的,你可以通过索引值来引用源线路,但是你必须知道每个目标线路的索引值。通过传递MIXER_GETLINEINFOF_SOURCE 标志给mixerGetLineInfo()函数,告知它你要通过索引值来引用线路。例如,如下展示怎样获取我们的样例混音器中的第一条源线路(目标线路为扬声器输出)。在调用mixerGetLineInfo()和传递MIXERLINE结构体之前,你必须初始化3个字段:cbStruct字段的值必须设置为你传递的MIXERLINE结构所占的字节数,dwSource字段必须设置为你想获取信息的源线路的索引值,dwDestination字段必须设置为和这个源线路相连的目标线路的索引值。
MIXERLINE mixerline;
MMRESULT err;
mixerline.cbStruct = sizeof(MIXERLINE);
mixerline.dwDestination = 0;
mixerline.dwSource = 0;
if ((err = mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerline, MIXER_GETLINEINFOF_SOURCE)))
{
printf("Error #%d calling mixerGetLineInfo()\n", err);
}
当上面的调用返回后, mixerGetLineInfo() 会根据内部CD音频(
mixerline_CD
)的MIXERLINE结构体填充我们的MIXERLINE结构体(毕竟,扬声器输出是我们样例混音器的第一条线路,它索引值为0)。现在根据示例,你可以从
MIXERLINE结构体的
dwLineID字段取得线路的ID。
现在,如果你想取得扬声器输出目标线路的第二条源线路的信息,唯一不同的就是你设置给
dwSource
字段的值是第二个源线路的索引(1),如下:
mixerline.cbStruct = sizeof(MIXERLINE);
mixerline.dwDestination = 0;
mixerline.dwSource = 1;
if ((err = mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerline, MIXER_GETLINEINFOF_SOURCE)))
{
printf("Error #%d calling mixerGetLineInfo()\n", err);
}
当上面的调用返回后, mixerGetLineInfo() 会根据合成器(
mixerline_Synth
)的MIXERLINE结构体填充我们的MIXERLINE结构体。
现在,你应该明白了如何根据索引值去枚举(每条目标线路的)源线路。下面是个示例,它打印出混音器中所有目标线路以及它们各自的所有源线路的名字:
MIXERCAPS mixcaps;
MIXERLINE mixerline;
MMRESULT err;
unsigned long i, n, numSrc;
if (!(err = mixerGetDevCaps((UINT)mixerHandle, &mixcaps, sizeof(MIXERCAPS))))
{
for (i = 0; i < mixercaps.cDestinations; i++)
{
mixerline.cbStruct = sizeof(MIXERLINE);
mixerline.dwDestination = i;
if (!(err = mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerline, MIXER_GETLINEINFOF_DESTINATION)))
{
printf("Destination #%lu = %s\n", i, mixerline.szName);
numSrc = mixerline.cConnections;
for (n = 0; n < numSrc; n++)
{
mixerline.cbStruct = sizeof(MIXERLINE);
mixerline.dwDestination = i;
mixerline.dwSource = n;
if (!(err = mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerline, MIXER_GETLINEINFOF_SOURCE)))
{
printf("\tSource #%lu = %s\n", i, mixerline.szName);
}
}
}
}
}
通过线路ID取得信息
一旦你知道了一条线路的ID(按照上面所示的方法枚举出线路,然后从MIXERLINE的dwLine字段取得),你就可以通过此ID来获取线路的信息(代替它的索引值)。如果你在处理一条源线路,你不需要知道其所连接的目标线路的索引值。你只需要初始化MIXERLINE结构体的dwLineID字段为想要的目标线路的ID值,然后在调用mixerGetLineInfo()的时候传递MIXER_GETLINEINFOF_LINEID参数,如下所示:
mixerline.cbStruct = sizeof(MIXERLINE);
mixerline.dwLineID = 0x00000003;
if ((err = mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerline, MIXER_GETLINEINFOF_LINEID)))
{
printf("Error #%d calling mixerGetLineInfo()\n", err);
}
以上方法均适用于源线路和目标线路。只要你知道了一
条
线路的ID,你就可以根据它直接获取线路的信息,而不需要知道其索引。
通过类型取得线路信息
通常,你不需要知道混音器中所有的线路。你的程序可能仅仅只和某个类型的线路打交道。比如,假设你正在编写一个MIDI文件播放器。现在,我们的样例声卡的某些元件对于你来说根本毫无用处。MIDI不是数字音频数据,因此DAC WAVE输入元件(以及所有连接到它的所有源线路)对你来说毫无意义。同样,目标线路扬声器输出的源线路内部CD音频、DAC WAVE输出和麦克风输入对你来说也毫无意义。我们的样例声卡上唯一一个可以处理MIDI数据回放的元件是连接扬声器输出的合成器元件。正是这个线路的控制器影响着MIDI数据的回放。
所以,与其枚举混音器中的所有线路直到碰到一
条
类型为MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER的线路,不如用mixerGetLineInfo()直接获取指定类型的线路信息。你只需要将MIXERLINE的dwComponentType字段指定为你想要的线路类型,然后在调用
mixerGetLineInfo()的时候
指定MIXER_GETLINEINFOF_COMPONENTTYPE即可,如下所示:
mixerline.cbStruct = sizeof(MIXERLINE);
mixerline.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER;
if ((err = mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerline, MIXER_GETLINEINFOF_COMPONENTTYPE)))
{
printf("Error #%d calling mixerGetLineInfo()\n", err);
}
系统将会用混音器中第一
条
类型为MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER的线路的信息填充此MIXERLINE结构体。(如果混音器中没有此类型的线路,将会返回一个MIXERR_INVALLINE的错误)。一旦获取了此线路的信息,你就可以操纵它的控制器了。这样当你的需求是明确的时候,就省去了通过枚举得到某个线路的麻烦。
MIXERCONTROL结构体,以及枚举控制器
你可以通过混音器API mixerGetLineControls()来获取线路中控制器的信息。这个API将会将某个控制器的信息填入到一个MIXERCONTROL结构体中。
我们首先来看一下MIXERCONTROL结构体。如前所述,我们的扬声器输出目标线路有两个控制器:一个滑动音量调节器和一个静音开关。每个控制器都有一个MIXERCONTROL结构体与其关连。我们首先看一下每个控制器的MIXERCONTROL结构:
MIXERCONTROL mixerctl_Spkr_Vol = {
sizeof(MIXERCONTROL),
0x00000000,
MIXERCONTROL_CONTROLTYPE_VOLUME,
MIXERCONTROL_CONTROLF_UNIFORM,
0,
"Volume",
"Speaker Out Volume",
0,
65535,
0, 0, 0, 0,
31,
0, 0, 0, 0, 0,
};
MIXERCONTROL mixerctl_Spkr_Mute = {
sizeof(MIXERCONTROL),
0x00000001,
MIXERCONTROL_CONTROLTYPE_MUTE,
MIXERCONTROL_CONTROLF_UNIFORM,
0,
"Mute",
"Speaker Out Mute",
0,
1,
0, 0, 0, 0,
0,
0, 0, 0, 0, 0,
};
上面有几个问题需要注意。
首先,注意每个控制器都有一个唯一的ID,这些ID不必和线路的ID不同(比如控制器mixerctl_Spkr_Vol的ID恰巧和线路mixerline_CD的ID相同),但是每个
控制器
的ID都不能和其它
控制器
的ID相同,包括同其他线路的所有
控制器
(例如,线路
扬声器输出
的
滑动
音量
调节器的ID不能和
ADC WAVE输入
线路的静音开关的ID相同)。
我已设置了MIXERCONTROL_CONTROLF_UNIFORM标志,这个标志意味着,虽然
扬声器输出
是立体声的(有两个声道),但是并不是每个
声道
都有一个单独的音量
控制器
(这里左右声道没有各自独立的音量设置)。对于这两个声道而言,只有一个共同的音量设置,因此这两个声道总是被设置为相同的音量(稍候我们会学非均衡(not uniform)的
控制器
)。
同样需要注意的是每个
控制器
都有一个适当的类型。
滑动
音量
调节
器
的类型为MIXERCONTROL_CONTROLTYPE_VOLUME,
静音开关
控制器
的类型为MIXERCONTROL_CONTROLTYPE_MUTE。
MIXERCONTROL还会告诉你这个
控制器可以设置
的最大值和最小值。例如,
滑动
音量
调节
器
可以设置0到65535之前的任何一个值。其中0是最小值(此时音量最小),65535是最大值(此时音量最大),这是不是意味着
滑动
音量
调节
器
有65535个离散的等级呢?(可以被设置为从0至65535之间(包括0和65535)的任何值?)。不一定。你还得看一下等级数(
step amount
)这个字段。它会告诉你这个
控制器
有多少个有效的等级。在目前情况下,我们有31个有效的等级。这意味着第一个有效的设置值是0,但是第二个有效的设置值是65,535 - (65,535/31) ,第三个有效的设置值是65,535 - (65,535/(31*2)),以此类推。换句话说,我们只能设置0到65535之间的31个值(注意:dwMinimum和dwMaximum字段同lMinimum和lMaximum字段在一个联合体中声明。在处理unsigned类型的值时,我们会用到前面的一组值dwMinimum和dwMaximum,比如处理类型为MIXERCONTROLDETAILS_BOOLEAN或
MIXERCONTROLDETAILS_UNSIGNED的
控制器
的值。在处理signed类型的值时,我们会用到后面一组值lMinimum和lMaximum,比如处理类型为MIXERCONTROLDETAILS_SIGNE的
控制器
的值)。
枚举
控制器
和枚举线路稍微有点不同。首先,你不必使用
控制器
的索引值。其次,你只有在知道某个
控制器
的ID的情况下才可以只取这个
控制器的信息。
否则,你必须同时获取给定线路的所有
控制器
的信息。
显然,当你第一次枚举某
条
线路的
控制器
时,你不知道每个
控制器
的ID。因此,你必须通过一个mixerGetLineControls()
调用
获取所有线路的信息。这样的话,你必须给
mixerGetLineControls()传递一个MIXERCONTROL结构的数组。对线路中每个
控制器
都必须有一个类型为MIXERCONTROL的结构。
例如,我们知道我们的
扬声器输出
目标线路有两个
控制器
:一个
滑动
音量
调节器和一个静音开关(记住MIXERLINE结构的cControls字段是2)。因此,如果要获取这两个
控制器
的信息,我们就必须给mixerGetLineControls传递包含两个MIXERCONTROL结构的数组。同时我们必须传递MIXER_GETLINECONTROLSF_ALL标志表明我们需要获取所有
控制器
的信息。为了告诉mixerGetLineControls()我们希望获取哪个线路的
控制器
的信息,我们还必须初始化并传递一个类型为MIXERLINECONTROLS的结构。同时我们提供一个指向我们
MIXERCONTROL结构体数组的指针作为额外的结构体。
下面是获取线路扬声器输出的所有
控制器
信息的示例:
MIXERCONTROL mixerControlArray[2];
MIXERLINECONTROLS mixerLineControls;
MMRESULT err;
mixerLineControls.cbStruct = sizeof(MIXERLINECONTROLS);
mixerLineControls.cControls = 2;
mixerLineControls.dwLineID = 0xFFFF0000;
mixerLineControls.pamxctrl = &mixerControlArray[0];
mixerLineControls.cbmxctrl = sizeof(MIXERCONTROL);
if ((err = mixerGetLineControls((HMIXEROBJ)mixerHandle, &mixerLineControls, MIXER_GETLINECONTROLSF_ALL)))
{
printf("Error #%d calling mixerGetLineControls()\n", err);
}
当mixerGetLineControls()返回后,我们的mixerControlArray[]数组里就保存着已填充的信息。mixerControlArray[0]将会按照上面显示的mixerctl_Spkr_Vol控制器的内容填充。mixerControlArray[1]会按照上面显示的mixerctl_Spkr_Mute
控制器
的内容填充。现在,你可以通过每个MIXERCONTROL的dwControlID字段获得每个控制器的ID。
如果你知道
控制器
的ID或类型的话,每次获取一个控制器的信息也是可以的。但是一次获取数目介于1(不包括1)和所有
控制器
总数(不包括总数)之间个
控制器
的信息是不行的。例如,假设我们的扬声器输出线路有5个
控制器
(而不是目前的2个),你不能仅仅获取前3个
控制器
。例如,你只能一次获取5个
控制器
中1个
控制器
的信息,或者一次获取着5个
控制器
的信息(要么一次一个
,要么一次所有
)。
通过控制器ID取得信息
一旦你知道了一个控制器的ID(你可以使用上面所示的方法,首先枚举所有控制器,然后从MIXERCONTROL的dwControlID字段获取控制器的ID),你就可以通过这个ID获取这个控制器的信息,甚至不必知道这个控制器所属的线路的ID。你也不必同时获取这条线路所有控制器的信息。你仅仅只需要初始化MIXERCONTROL的dwControlID字段,然后在调用mixerGetLineControls()的时候指定MIXER_GETLINECONTROLSF_ONEBYID标志,如下:
MIXERCONTROL mixerControlArray;
MIXERLINECONTROLS mixerLineControls;
MMRESULT err;
mixerLineControls.cbStruct = sizeof(MIXERLINECONTROLS);
mixerLineControls.cControls = 1;
mixerLineControls.dwControlID = 0x00000000;
mixerLineControls.pamxctrl = &mixerControlArray;
mixerLineControls.cbmxctrl = sizeof(MIXERCONTROL);
if ((err = mixerGetLineControls((HMIXEROBJ)mixerHandle, &mixerLineControls, MIXER_GETLINECONTROLSF_ONEBYID)))
{
printf("Error #%d calling mixerGetLineControls()\n", err);
}
通过类型取得控制器信息
通常情况下,你不必知道某个线路中所有控制器的信息。在你的程序中你可能仅仅只和某个类型的控制器打交道。比如,假设你在写一个简单的MIDI文件回放程序,你提供给终端用户的只是音乐合成器的一个滑动音量调节器。前面我们已经知道怎样通过类型获取MIDI回放线路并获取此线路的信息,比如线路ID。你可以用这个线路ID来搜索此线路中某个特定类型的控制器,比如你可以寻找一个类型为MIXERCONTROL_CONTROLTYPE_VOLUME的控制器。
所以,与其通过枚举线路中所有控制器直到你碰到一个类型为MIXERCONTROL_CONTROLTYPE_VOLUME的
控制器
,不如用mixerGetLineControls()直接通过类型获取
控制器
。你只需要将MIXERCONTROLS的字段dwControlType设定为你想要的类型,然后在
调用mixerGetLineControls()的时候
指定MIXER_GETLINECONTROLSF_ONEBYTYPE标志(假设你已经获取了合成器线路的ID,并存储在
变量
SynthID中)
:
MIXERCONTROL mixerControlArray;
MIXERLINECONTROLS mixerLineControls;
MMRESULT err;
mixerLineControls.cbStruct = sizeof(MIXERLINECONTROLS);
mixerLineControls.dwLineID = SynthID;
mixerLineControls.cControls = 1;
mixerLineControls.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
mixerLineControls.pamxctrl = &mixerControlArray;
mixerLineControls.cbmxctrl = sizeof(MIXERCONTROL);
if ((err = mixerGetLineControls((HMIXEROBJ)mixerHandle, &mixerLineControls, MIXER_GETLINECONTROLSF_ONEBYTYPE)))
{
printf("Error #%d calling mixerGetLineControls()\n", err);
}
系统将会使用此线路中第一个拥有MIXERCONTROL_CONTROLTYPE_VOLUME类型的
控制器
的信息来填充MIXERCONTROL结构体。(如果此线路中没有此类型的
控制器
,将会返回一个错误值MIXERR_INVALCONTROL)。一旦你获取到了
控制器
的信息,就可以直接操作此控制器了。例如,你可以从MIXERCONTROL的字段dwControlID获取
控制器
的ID。这样,当你想要某个类型的
控制器
时就不用枚举所有的
控制器
了。
获取和设置控制器的值
现在,我们来讲混音器的最终目的:获取一个控制器的值(这样你就可以将其当前的值显示给终端用户),和设置一个
控制器
的值(这样可以让终端用户调整
控制器
的值)。
想要获取或设置某个
控制器
的值,你必须知道
控制器
的ID。然后就可以用mixerGetControlDetails()获取控制器当前值,用mixerSetControlDetails()设置某个特定值。这些函数都使用一个类型为MIXERCONTROLDETAILS的结构体。你可以初始化其中的部分字段来告诉
mixerGetControlDetails()/mixerSetControlDetails()你想设置/获取哪个
控制器
的值。你同时还得提供指向另外一个即将存放值的结构的指针。
例如,我们获取扬声器输出线路的
滑动
音量调节器的当前值。到现在为止,我们已知道了怎样获取这个控制器的信息(例如
控制器
的ID)。为了获取
控制器
的值,我们需要提供一个特殊的结构体来存放返回值。
我们需要使用什么样的结构?哦,这要看
控制器
的类型了。
滑动
音量调节器
的类型为MIXERCONTROL_CONTROLTYPE_VOLUME,如果你回头看一下关于调节器(Fader)类和控制器的图表,就知道了这个值将使用一个类型为MIXERCONTROLDETAILS_UNSIGNED的结构。这个结构只有一个字段dwValue,用于存放返回值。因此我们提供一个MIXERCONTROLDETAILS_UNSIGNED类型的结构给mixerGetControlDetails()(通过MIXERCONTROLDETAILS结构),如下是一个样例程序,获取并打印扬声器输出线路中
滑动
音量调节器
的当前值。
MIXERCONTROLDETAILS_UNSIGNED value;
MIXERCONTROLDETAILS mixerControlDetails;
MMRESULT err;
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
mixerControlDetails.dwControlID = 0x00000000;
mixerControlDetails.cChannels = 1;
mixerControlDetails.cMultipleItems = 0;
mixerControlDetails.paDetails = &value;
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
if ((err = mixerGetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails, MIXER_GETCONTROLDETAILSF_VALUE)))
{
printf("Error #%d calling mixerGetControlDetails()\n", err);
}
else
{
printf("It's value is %lu\n", value.dwValue);
}
若要设置一个控制器的值,你只需填充此结构,然后将其传递给mixerSetControlDetails()。你同时还要指定MIXER_SETCONTROLDETAILSF_VALUE结构。这里是一个样例程序,其将
扬声器输出
线路的
滑动
音量调节器
的值设置为31。
MIXERCONTROLDETAILS_UNSIGNED value;
MIXERCONTROLDETAILS mixerControlDetails;
MMRESULT err;
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
mixerControlDetails.dwControlID = 0x00000000;
mixerControlDetails.cChannels = 1;
mixerControlDetails.cMultipleItems = 0;
mixerControlDetails.paDetails = &value;
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
value.dwValue = 31;
if ((err = mixerSetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails, MIXER_SETCONTROLDETAILSF_VALUE)))
{
printf("Error #%d calling mixerSetControlDetails()\n", err);
}
多声道控制器
前面已说过,当将设置了控制器的MIXERCONTROL_CONTROLF_UNIFORM标志时,所有声道都共享同一个值。例如,对于扬声器输出线路,它是立体声线路,但是其左右声道并没有独立的音量值。
但是,若一个
控制器
的MIXERCONTROL_CONTROLF_UNIFORM值没有设置,并且此
控制器
有一个以上的声道,那么每个声道都有一个独立的值。这样,当你设置/获取某个
控制器
的值时,你必须提供多个特殊的结构来获取或设置所有声道的值。例如,假设扬声器输出线路的滑动
音量调节
器没有设置
MIXERCONTROL_CONTROLF_UNIFORM标志,由于此线路有两个声道,所以我们必须提供两个MIXERCONTROLDETAILS_UNSIGNED结构来存放获取/设置其左右声道的值,我们需要使用一个类型为MIXERCONTROLDETAILS_UNSIGNED的数组。第一个MIXERCONTROLDETAILS_UNSIGNED结构将存放第一个声道(左)的值,第二个结构将存放第二个声道(右)的值。如下获取我们的扬声器输出线路的滑动音量调节器的左右声道的值的一个示例:
MIXERCONTROLDETAILS_UNSIGNED value[2];
MIXERCONTROLDETAILS mixerControlDetails;
MMRESULT err;
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
mixerControlDetails.dwControlID = 0x00000000;
mixerControlDetails.cChannels = 2;
mixerControlDetails.cMultipleItems = 0;
mixerControlDetails.paDetails = &value[0];
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
if ((err = mixerGetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails, MIXER_GETCONTROLDETAILSF_VALUE)))
{
printf("Error #%d calling mixerGetControlDetails()\n", err);
}
else
{
printf("The left channel's volume is %lu\n", value[0].dwValue);
printf("The right channel's volume is %lu\n", value[1].dwValue);
}
为设置全部声道的值,你需要填充MIXERCONTROLDETAILS_UNSIGNED 结构体。下面是个示例,设置左声道音量为31,右声道音量为0.
MIXERCONTROLDETAILS_UNSIGNED value[2];
MIXERCONTROLDETAILS mixerControlDetails;
MMRESULT err;
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
mixerControlDetails.dwControlID = 0x00000000;
mixerControlDetails.cChannels = 2;
mixerControlDetails.cMultipleItems = 0;
mixerControlDetails.paDetails = &value[0];
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
value[0].dwValue = 31;
value[1].dwValue = 0;
if ((err = mixerSetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails, MIXER_SETCONTROLDETAILSF_VALUE)))
{
printf("Error #%d calling mixerSetControlDetails()\n", err);
}
当然,一个控制器或许有2个以上的声道。对一个给定的控制器,你必须提供足够大的结构来容纳所有的声道的值。因此,通常你必须根据需要来开辟数组空间。
一次存取一个控制器中的某几个声道是非法的。例如,一个控制器有8个声道,但是你只取其前2个声道的值,这是不允许的。你必须同时存取一个控制器的所有声道才行。但是这里有一条有关设置一个值的规则:如果你仅仅设置第一个声道的值,那么mixerSetControlDetails()自动将控制器设置MIXERCONTROL_CONTROLF_UNIFORM标志。最终结果是此控制器的所有声道都被设置为这个值。因此,你可以通过仅仅设置第一个声道的值来达到将所有的声道设置为同一个值的目的。
多元素控制器
你不会常常碰到多元素的控制器。一个多元素控制器就是每个声道关联着多个值的控制器。图形化均衡器是一个例子。让我们来看一个简单的,假设一个声卡内置有以下带着3个通道(band)的图形化均衡器:
这个控制器有3个值与其关联—与“低通道”关联的值、与“中通道”关联的值、与“高通道”关联的值(假设每个通道都能设置为不同的值,否则它就是一个无用的图形
化
均衡器了)。这样表现出来的就是一个
多元素控制器
,它有3个元素(值)与其关联。
我们再进一步假设这个
控制器
属于扬声器输出线路。
我们来看一下MIXERCONTROL结构体。一个
多元素控制器
的MIXERCONTROL的字段dwControlType的值会设置有MIXERCONTROL_CONTROLF_MULTIPLE位标志,
同时
MIXERCONTROL的cMultipleItems字段会告诉你每个声道有几个元素。
MIXERCONTROL mixerctl_EQ = {
sizeof(MIXERCONTROL),
0x00000002,
MIXERCONTROL_CONTROLTYPE_EQUALIZER,
MIXERCONTROL_CONTROLF_UNIFORM|MIXERCONTROL_CONTROLF_MULTIPLE,
3,
"EQ",
"Graphic Equalizer",
0,
65535,
0, 0, 0, 0,
31,
0, 0, 0, 0, 0,
};
首先,注意控制器ID不同于此混音器中其它
控制器
的ID。同样要注意的是,这里还设置了MIXERCONTROL_CONTROLF_MULTIPLE位标志。cMultipleItems被设置为3,表明每个声道有3个元素(但是即使我已将此
控制器
设置了MIXERCONTROL_CONTROLF_UNIFORM标志,总共仍然只有3个值,虽然扬声器输出线路是立体声的。换句话说,每个通道的值相同地影响着所有声道)。
为了获取3个通道的值,我们需要一个包含3个结构的数组。需要什么类型的结构呢?恩,类型为MIXERCONTROL_CONTROLTYPE_EQUALIZER的
控制器
的类是调节器类(Fader,即MIXERCONTROL_CT_CLASS_FADER),你会想起这个类(Class)的所有类型(Type)的值都使用MIXERCONTROLDETAILS_UNSIGNED类型的结构。如下演示怎样获取3个通道的值:
MIXERCONTROLDETAILS_UNSIGNED value[3];
MIXERCONTROLDETAILS mixerControlDetails;
MMRESULT err;
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
mixerControlDetails.dwControlID = 0x00000002;
mixerControlDetails.cChannels = 1;
mixerControlDetails.cMultipleItems = 3;
mixerControlDetails.paDetails = &value[0];
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
if ((err = mixerGetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails, MIXER_GETCONTROLDETAILSF_VALUE)))
{
printf("Error #%d calling mixerGetControlDetails()\n", err);
}
else
{
printf("The Low band is %lu\n", value[0].dwValue);
printf("The Mid band is %lu\n", value[1].dwValue);
printf("The High band is %lu\n", value[2].dwValue);
}
为设置这3个通道的值,你需要填充MIXERCONTROLDETAILS_UNSIGNED 结构体。下面是个示例,其设置低通道为31,中通道为0,高通道为62。
MIXERCONTROLDETAILS_UNSIGNED value[3];
MIXERCONTROLDETAILS mixerControlDetails;
MMRESULT err;
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
mixerControlDetails.dwControlID = 0x00000002;
mixerControlDetails.cChannels = 1;
mixerControlDetails.cMultipleItems = 3;
mixerControlDetails.paDetails = &value[0];
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
value[0].dwValue = 31;
value[1].dwValue = 0;
value[2].dwValue = 62;
if ((err = mixerSetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails, MIXER_SETCONTROLDETAILSF_VALUE)))
{
printf("Error #%d calling mixerSetControlDetails()\n", err);
}
现在我们将控制器的MIXERCONTROL_CONTROLF_UNIFORM标志去掉。因此,每个声道的每个元素都有其自己的值。因为扬声器输出有两个声道,那意味着我们的控制器需要总计为2 (声道) * 3 (元素)个结构,也就是6个值。我们的图形化均衡器如下图所示:
![混音器原理及Mixer API函数介绍3 - 我,猪八戒 - 我,猪八戒的博客](https://i-blog.csdnimg.cn/blog_migrate/269dce06d5c8ab9344ae27513538abaf.png) | ![混音器原理及Mixer API函数介绍3 - 我,猪八戒 - 我,猪八戒的博客](https://i-blog.csdnimg.cn/blog_migrate/ce5cffadefefab039f07facba57685f3.gif) |
Left Channel | Right Channel |
左声道右声道
我们需要6个类型为MIXERCONTROLDETAILS_UNSIGNED的结构来存放所有声道的所有元素的值。对了,在前面的样例中,我都将这些元素的标签设置过了。如果你希望将它们输出,你就应该向混音器查询这些值。为了达到目的,你必须提供一个类型为MIXERCONTROLDETAILS_LISTTEXT结构的数组,就如同你提供一组类型为MIXERCONTROLDETAILS_UNSIGNED的结构来获取所有声道的所有元素的值一样。
MIXERCONTROLDETAILS_UNSIGNED value[6];
MIXERCONTROLDETAILS_LISTTEXT label[6];
MIXERCONTROLDETAILS mixerControlDetails;
MMRESULT err;
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
mixerControlDetails.dwControlID = 0x00000002;
mixerControlDetails.cChannels = 2;
mixerControlDetails.cMultipleItems = 3;
mixerControlDetails.paDetails = &value[0];
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
if ((err = mixerGetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails, MIXER_GETCONTROLDETAILSF_VALUE)))
{
printf("Error #%d calling mixerGetControlDetails()\n", err);
}
else
{
unsigned long i,n;
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
mixerControlDetails.dwControlID = 0x00000002;
mixerControlDetails.cChannels = 2;
mixerControlDetails.cMultipleItems = 3;
mixerControlDetails.paDetails = &label[0];
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_LISTTEXT);
if ((err = mixerGetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails, MIXER_GETCONTROLDETAILSF_LISTTEXT)))
{
printf("Error #%d calling mixerGetControlDetails()\n", err);
}
else
{
for (i = 0; i < 2; i++)
{
printf("Channel %lu:\n", i+1);
for (n = 0; n < 3; n++)
{
printf("\tThe %s item is %lu\n", label[3 * i + n].szName, value[3 * i + n].dwValue);
}
}
}
}
一次获取或设置某个控制器的几个元素的值是不支持的。例如,若一个控制器有8个元素,你尝试仅仅获取其前2个元素的信息是不允许的。你必须同时设置/获取所有的声道的所有元素的值。关于设置元素的值,这里有一个规则:如果你仅仅设置第一个声道的元素的值,那么mixerSetControlDetails()会自动将控制器设置MIXERCONTROL_CONTROLF_UNIFORM标记。最终结果就是所有声道的所有元素的值都和第一个声道的元素的值相同。因此,你可以通过只设置第一个声道的元素的值达到快速将所有的声道的元素的值设为相同值的目的。
更改通知
在上面的样例程序中,我已展示了通过mixerOpen()函数打开混音器然后将其返回值用于其它混音器函数。不一定非得这样做。实际上,混音器API被设计为可以按以下方式使用:你可以传递混音器ID,而不必将打开的混音器句柄作为混音器函数的某个参数去传递。所以,你不必显式的打开一个混音器。
但是如果你想在混音器上做某些操作,显式的打开混音器设备(通过mixerOpen()函数)还是会有一些好处的。
首先,这会防止混音器被卸载(大概是声卡的驱动所为)。其次,在你打开了一个混音器后,当此混音器的任何线路的状态发生改变时(比如线路被设置为静音),或者其中某个控制器的值改变时,你可以指示windows系统发送一个消息(到你创建的自定义的窗口处理例程)通知你。你不仅仅会在你改变某个线路的状态或某个控制器的值时收到这些消息,而且当其它程序打开混音器(多个程序可以同时打开一个混音器)并改变某个线路的状态或某个控制器的值时也会收到。因此,当其它程序对混音器做改变时,你可以使你的程序与混音器的状态保持同步。
当你调用mixerOpen()时,你应该将接受通知消息的窗口句柄作为第三个参数传递给此函数,并将CALLBACK_WINDOW指定为最后一个参数。
这里有2个特殊的“混音器消息”。
MM_MIXM_LINE_CHANGE:当混音器的任何一
条
线路的状态发生改变时,系统会发送此消息到你的窗口处理程序。
MM_MIXM_CONTROL_CHANGE:当混音器中的任何一个控制器的值发生改变时,系统会发送此消息到你的窗口处理程序。
对于MM_MIXM_LINE_CHANGE,WPARAM参数表示发生改变的线路所属的混音器句柄,LPARAM参数表示此线路的ID。
对于MM_MIXM_CONTROL_CHANGE,WPARAM参数表示发生改变的线路所属的混音器句柄,LPARAM参数表示值发生改变的控制器的ID。
总结
混音器API是windows多媒体API中最复杂的一组。你可能需要一点时间来吸收这篇教程然后将它应用于你的程序中。但是混音器API使得你可以操作任何已安装的声卡,而不必对某个特定的声卡编写特定的程序。
想获取更多关于混音器结构和API的信息,请参考Microsoft Developer Network上关于音频混音器的文章(audio mixers) 。
微软提供了一个免费下载的关于如何使用混音器API的样例程序。但是我发现它的代码的注释太简单了,同时有很多和混音器API无关
的
且不必要的代码。我已将此样例程序精简,使它展示的都是如何使用混音器API的关键代码,并添加了很多注释。你可以下载我修改的版本Microsoft's Mixer Device Example,它将展示如何显示所有的混音器设备和它们的线路/
控制器
,以及调整它们
控制器
的值。这个工程是基于Visual C++4.0的,因为它是一个普通的用C语言编写的windows应用程序,因此任何windows的C语言编译器应该都可以编译它。