为了理解Mixer API是如何工作的,首先我们得弄清楚一个典型声卡的硬件组成。因此非常有必要去建立一个声卡模型,此声卡应拥有多个典型的组件并且这些组件都是相关联的。
让我们看一个典型的、最基本的声卡。首先,如果声卡能够进行数字化录音,那么典型情况下它就有一个MicrophoneInput(麦克风传声器,下同)(附有某种前置放大器),同时它还有一个ADC(模数转换器,下同)将麦克风输入的模拟信号转换为数字信号,因此,它就有两个组件——MicrophoneInput和ADC。从MicrophoneInput组件输入的信号输送到ADC。我们可以使用以下的方块图来表示这两个组件,并表明信号在这两个组件之间的传输关系(通过箭头表示)。
<!--[if gte vml 1]> <![endif]--> |
一个典型的声卡应该还可以回放数字声音,因此它有一个DAC(数模转换器,下同)组件将数字信号转换回模拟信号,同时它还应有一个Speaker Out(扬声器,下同)(附有某种模拟信号放大器)。因此,它又添加了两个组件——DAC和Speaker Out。从DAC输出的信号输送到Speaker Out中。
|
|
一个典型的声卡或许还有其它组件。例如,它或许有一个能播放MIDI音频的内置声音模块(比如Synth(合成器,下同))。这个组件的声音输出同DAC的输出同样输入到SpeakerOut组件中。因此,我们的方框图现在是如下的样子:
<!--[if gte vml 1]> <![endif]--> alt="混音器原理及Mixer API函数介绍" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1027"> | <!--[if gte vml 1]> <![endif]--> alt="混音器原理及Mixer API函数介绍" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1028"> | <!--[if gte vml 1]> <![endif]--> alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1029"> |
同样的,一个典型的声卡还有一个内部的连接器连接着计算机的CDROM驱动器的声音输出(这样就可以通过扬声器来播放CDROM里的CD)。与Synth和DAC一样,这个组件的输出会输入到SpeakerOut。现在,我们的方块图就成了如下的样子:
<!--[if gte vml 1]> <![endif]--> alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1030"> | <!--[if gte vml 1]> <![endif]--> alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1031"> | <!--[if gte vml 1]> <![endif]--> | <!--[if gte vml 1]> <![endif]--> alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1033"> |
最后,我们假设这个声卡还有一个Line In(线路输入,下同)组件,因此其它外部的录音机或音频设备或外部的硬件混音器就能够连接到此插孔并将其输入信号数字化。同MicrophoneInput组件一样,这个组件的输出会输送到ADC组件。下面是我们最终的方块图,其中包括7个组件(和5个信号流向线图——也就是将各个组件相连的箭头线)
<!--[if gte vml 1]> <![endif]--> alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1034"> | <!--[if gte vml 1]> <![endif]--> alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1035"> | <!--[if gte vml 1]> <![endif]--> alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1036"> | <!--[if gte vml 1]> <![endif]--> alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1037"> | <!--[if gte vml 1]> <![endif]--> alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1038"> |
典型情况下,每个组件都有它自己的参数。例如,Synth通常会有它的音量参数。Internal CD Audio同样有它自己的音量参数。DAC也会有它自己的音量参数。在这种方式下,如果用户同时播放一个Audio CD、一个MIDI文件和一个WAVE文件,他可以分别调整这三个组件输入到Speaker Out的音量。同样的,Speaker Out组件也有它自己的音量参数——控制上述3个输入组件最终输出的主音量。
同样的,Line In和Microphone Input组件也都有各自的音量参数,这样在同时录音的情况下,就可以平衡二者的输入。ADC组件有个主音量参数,它控制着上述2个连接到它的输入组件的录音音量。
一个给定的组件还拥有其它可控的参数。例如,上述每个组件都有各自的用于快速打开或关闭声音的静音(Mute)开关。
混音器设备
系统中每个声卡都有一个与其相连的混音器设备。声卡上所有的组件都由与声卡关联的混音器设备控制。WindowsMixer 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组件的音量。(第一块声卡的混音器当然控制不了第二块声卡的WaveOutput组件了)。
但是,你怎样打开与你想要的那块声卡关联的混音器?幸运的是,mixerOpen()函数允许你传递与这个声卡关联的其它设备的ID或句柄来打开混音器。在这种情况下,mixerOpen()函数将会准确无疑的返回与那个声卡的其它设备关联的混音器设备。以下是一个样例程序,展示了如何通过打开默认的WAVE OUT设备(默认声卡上的WAVE OUT设备)获取混音器设备的句柄:
unsigned long err;
HMIXER mixerHandle;
WAVEFORMATEXwaveFormat;
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 longmixerID;
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 longiNumDevs, 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个源线路,从此,只要你看到“源线路”,就将它想象为组件之间的信号流。
例如,实际上不是MicrophoneInput组件与Mixer API相关联,而是Microphone Input组件和ADC组件之间的信号流与Mixer API关联。Microphone Input音量控件调整着Microphone Input组件和ADC组件间的信号流的音量。
为了更进一步说明“组件”和“源线路”之间的差别,我们再添加一个功能到我们的样例声卡中。有时,你或许希望MicrophoneInput组件输入的信号不仅仅能够输送到ADC(因此你可以录音),还可以输送到Speaker Out组件中(这样你就可以监视你正在录制的信号,通过Speaker Out组件能够听到它是个什么样子),因此,我们添加一个方块图来展示信号从Microphone Input组件同时输送到ADC组件和SpeakerOut组件。
<!--[if gte vml 1]> <![endif]--> alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1039"> | <!--[if gte vml 1]> <![endif]--> alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1040"> | <!--[if gte vml 1]> <![endif]--> alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1041"> | <!--[if gte vml 1]> <![endif]--> alt="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" title="混音器原理及Mixer API函数介绍1 - 我,猪八戒 - 我,猪八戒的博客" v:shapes="_x0000_i1042"> | <!--[if gte vml 1]> <![endif]--> |
注意我们现在已经有了6条源线路。有两个源线路引出自Microphone Input组件,即使它只是我们声卡上的一个组件。每个源线路都有它自己的设置。例如,
在MicrophoneInput和ADC间的源线路上有一个音量控件(这样你就可以设置录音音量)。在MicrophoneInput和Speaker Out间的源线路上还有一个单独的音量控件(这样你就可以设置Microphone Input组件的监控音量,与录音音量分离开来)。这些源线路还可以有各自的静音开关(因此你可以让MicrophoneInput的信号仅仅输送到ADC,而不输送到Speaker Out)。它们还可以拥有其它的控件,并且每个源线路的控件都和其它线路的控件相分离。
这里还要讨论另外一个和线路相关的重要概念。这里有一些名为“目标线路”的东西,我们要区分源线路和目标线路。它们是些什么东西?哦,一个目标线路有其它的线路——源线路——流向它。在我们的方块图中,SpeakerOut是一个目标线路,因为来自“Internal CD Audio”、“Synth”、“DAC WaveOut”和“Microphone Input”的信号会输送到它里面,后4个是源线路。他们是目标线路SpeakerOut的源线路,因为他们的信号流都输送到了Speaker Out线路。源线路的信号总是输送到目标线路(注:不存在一个没有与目标线路关联的源线路)。
因此,源线路就是我们方块图中的箭头,目标线路就是方块图中的实际组件。
同理,ADC Wave Input是我们样例声卡中的一个目标线路。它有两个源线路——“Microphone Input”和“Line In”。那两条线路不是SpeakerOut的源线路,因为它们不流向Speaker Out。同样地,流向Speaker Out的4根源线路也不是目标线路ADC Wave Input的源线路,因为它们不流向后者。
同源线路一样,目标线路也有控件,并且每个目标线路的控件和其它线路的控件是分离的。例如,SpeakerOut可以有一个音量控件。它可以作为一个主音量控件控制其它4条流向Speaker Out的源线路的音量。(这四个源线路都有它自己的音量控件)。
注意:虽然一个声卡可以有一个立体声组件(例如,大部分声卡上的Speaker Out组件一般都是立体声),但是还是被当作一条线路。它有两个声道,但是在混音器中它还是一条线路。实际上,一个组件可以拥有2个以上的声道,但仍然被当作一条线路。总之,线路同声道是不同的东西。在有些情况下,你可以将一条线路想象成一路MIDI。一路MIDI连接着两个组件,但是可以有多个声道同时通过这路MIDI。这个情况与线路一样。
总之,我们的样例声卡有6个源线路和2个目标线路。4个标为“InternalCD Audio”、“Synth”、“DAC Wave Out”和“Microphone Input”的源线路连接到SpeakerOut目标线路。2个标为“Microphone Input”和“Line In”的源线路连接到ADC WaveInput目标线路。
线路ID和类型
混音器中的每个线路必须有一个唯一的ID。每个线路还有一个类型值。这仅仅是一个描述线路类型的数值。这些都定义在头文件MMSYSTEM.H中。例如,一个类型值为MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER的线路表明它是来自内部声音模块的信号。
可用的源线路类型值如下:
MIXERLINE_COMPONENTTYPE_SRC_DIGITAL | A digital source, for example, a SPDIF input jack. |
MIXERLINE_COMPONENTTYPE_SRC_LINE | A line input source. Typically used for a line input jack, if there is a separate microphone input (ie, MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE). |
MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE | Microphone input (but also used for a combination of Mic/Line input if there isn't a separate line input source). |
MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER | Musical synth. Typically used for a card that contains a synth capable of playing MIDI. This would be the audio out of that built-in synth. |
MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC | The audio feed from an internal CDROM drive (connected to the sound card). |
MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE | Typically used for a telephone line's incoming audio to be piped through the computer's speakers, or the telephone line in jack for a built-in modem. |
MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER | Typically, to allow sound, that normally goes to the computer's built-in speaker, to instead be routed through the card's speaker output. The motherboard's system speaker connector would be internally connected to some connector on the sound card for this purpose. |
MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT | Wave playback (ie, this is the card's DAC). |
MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY | An aux jack meant to be routed to the Speaker Out, or to the ADC (for WAVE recording). Typically, this is used to connect external, analog equipment (such as tape decks, the audio outputs of musical instruments, etc) for digitalizing or playback through the sound card. |
MIXERLINE_COMPONENTTYPE_SRC_ANALOG | May be used similiarly to MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY (although I have seen some mixers use this like MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER). In general, this would be some analog connector on the sound card which is only accessible internally, to be used to internally connect some analog component inside of the computer case so that it plays through the speaker out. |
MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED | Undefined type of source. If none of the others above are applicable. |
可用的目标线路类型值如下:
MIXERLINE_COMPONENTTYPE_DST_DIGITAL | A digital destination, for example, a SPDIF output jack. |
MIXERLINE_COMPONENTTYPE_DST_LINE | A line output destination. Typically used for a line output jack, if there is a separate speaker output (ie, MIXERLINE_COMPONENTTYPE_DST_SPEAKERS). |
MIXERLINE_COMPONENTTYPE_DST_MONITOR | Typically a "Monitor Out" jack to be used for a speaker system separate from the main speaker out. Or, it could be some built-in monitor speaker on the sound card itself, such as a speaker for a built-in modem. |
MIXERLINE_COMPONENTTYPE_DST_SPEAKERS | The audio output to a pair of speakers (ie, the Speaker Out jack). |
MIXERLINE_COMPONENTTYPE_DST_HEADPHONES | Typically, a headphone output jack. |
MIXERLINE_COMPONENTTYPE_DST_TELEPHONE | Typically used to daisy-chain a telephone to an analog modem's "telephone out" jack. |
MIXERLINE_COMPONENTTYPE_DST_WAVEIN | The card's ADC (to digitize analog sources, for example, in recording WAVE files of such). |
MIXERLINE_COMPONENTTYPE_DST_VOICEIN | May be some sort of hardware used for voice recognition. Typically, a microphone source line would be attached to this. |
MIXERLINE_COMPONENTTYPE_DST_UNDEFINED | Undefined type of destination. If none of the others above are applicable. |
控件ID和类型
每个线路都有一个或多个可调整的“声音控件”。(但是,一个线路也可能连一个控件都没有)。例如,“Synth”线路可以有一个音量控件和一个静音开关。每个控件都有一个类型值。这些都在MMSYSTEM.H中定义。例如:
音量控件的类型为MIXERCONTROL_CONTROLTYPE_VOLUME。
静音控件的类型为MIXERCONTROL_CONTROLTYPE_MUTE。
每个控件都有它自己唯一的ID,任何两个控件都不会有相同的ID,即使它们属于不同的线路。
控件类型被分为几个种类。这些种类是大略的根据控件所调整的值的类型来划分,因此它基本决定了你提供什么样的图形界面给用户来调整控件的值。例如,你可以提供一个滑动条给用户调整类型为MIXERCONTROL_CONTROLTYPE_VOLUME的控件的值。另外一方面,你可以提供一个图形按钮(代码标记)给用户调整类型为MIXERCONTROL_CONTROLTYPE_MUTE的控件的值(因为那个控件只有两个可能的值或状态)。
可用的控件类的值如下:
MIXERCONTROL_CT_CLASS_FADER | A control that is adjusted by a vertical fader, with a linear scale of positive values (ie, 0 is the lowest possible value). A MIXERCONTROLDETAILS_UNSIGNED structure is used to retrieve or set the control's value. |
MIXERCONTROL_CT_CLASS_LIST | A control that is adjusted by a listbox containing numerous "values" to be selected. The user will single-select, or perhaps multiple-select if desired, his choice of value(s). A MIXERCONTROLDETAILS_BOOLEAN structure is used to retrieve or set the control's value. A MIXERCONTROLDETAILS_LISTTEXT structure is also used to retrieve the text description of each item of this control. |
MIXERCONTROL_CT_CLASS_METER | A control that is adjusted by a graphical meter. A MIXERCONTROLDETAILS_BOOLEAN, MIXERCONTROLDETAILS_SIGNED, or MIXERCONTROLDETAILS_UNSIGNED structure is used to retrieve or set the control's value. |
MIXERCONTROL_CT_CLASS_NUMBER | A control that is adjusted by numeric entry. The user enters a signed integer, unsigned integer, or integer decibel value. A MIXERCONTROLDETAILS_SIGNED or MIXERCONTROLDETAILS_UNSIGNED structure is used to retrieve or set the control's value. |
MIXERCONTROL_CT_CLASS_SLIDER | A control that is adjusted by a horizontal slider with a linear scale of negative and positive values. (ie, Generally, 0 is the mid or "neutral" point). A MIXERCONTROLDETAILS_SIGNED structure is used to retrieve or set the control's value. |
MIXERCONTROL_CT_CLASS_SWITCH | A control that is has only two states (ie, values), and is therefore adjusted via a button. A MIXERCONTROLDETAILS_BOOLEAN structure is used to retrieve or set the control's value. |
MIXERCONTROL_CT_CLASS_TIME | A control that allows the user to enter a time value, such as Reverb Decay Time. It is a positive, integer value. |
MIXERCONTROL_CT_CLASS_CUSTOM | A custom class of control. If none of the others above are applicable. |
每个类都有特定的类型值与其关联,例如MIXERCONTROL_CT_CLASS_FADER 种类有以下5个值与其关联:
MIXERCONTROL_CONTROLTYPE_VOLUME | Volume fader. The range of allowable values is 0 through 65,535. |
MIXERCONTROL_CONTROLTYPE_BASS | Bass boost fader. The range of allowable values is 0 through 65,535. |
MIXERCONTROL_CONTROLTYPE_TREBLE | Treble boost fader. The range of allowable values is 0 through 65,535. |
MIXERCONTROL_CONTROLTYPE_EQUALIZER | A graphic EQ. The range of allowable values for each band is 0 through 65,535. A MIXERCONTROLDETAILS_LISTTEXT structure is used to retrieve the text label for each band of the EQ. Typically, this control will also have its MIXERCONTROL_CONTROLF_MULTIPLE flag set, since the EQ will likely have numerous bands. |
MIXERCONTROL_CONTROLTYPE_FADER | A generic fader, to be used when none of the above are applicable. The range of allowable values is 0 through 65,535. |
实际上,如果你看一下MMSYSTEM.H,你就会发现类型为
MIXERCONTROL_CONTROLTYPE_VOLUME的控件被定义为
MIXERCONTROL_CT_CLASS_FADER| MIXERCONTROL_CT_UNITS_UNSIGNED + 1。
种类的值实际上包含在类型的高4位中。因此,如果你知道一个控件的类型,你就可以通过此类型值的高4位确定它的种类。例如,假设你已查询了一个控件的类型,并且将Mixer API返回的值保存在名为“type”的变量中,如下是你确定它的种类的代码:
unsigned long type;
/* 通过type的高4位判断控件的种类 */
switch (MIXERCONTROL_CT_CLASS_MASK& type)
{
case MIXERCONTROL_CT_CLASS_FADER:
printf("It's afader class.");
break;
case MIXERCONTROL_CT_CLASS_LIST:
printf("It's a listclass.");
break;
case MIXERCONTROL_CT_CLASS_METER:
printf("It's a meterclass.");
break;
case MIXERCONTROL_CT_CLASS_NUMBER:
printf("It's a numberclass.");
break;
case MIXERCONTROL_CT_CLASS_SLIDER:
printf("It's a sliderclass.");
break;
case MIXERCONTROL_CT_CLASS_TIME:
printf("It's a timeclass.");
break;
case MIXERCONTROL_CT_CLASS_CUSTOM:
printf("It's a customclass.");
break;
}
种类MIXERCONTROL_CT_CLASS_SWITCH有以下7个类型与其相关联:
MIXERCONTROL_CONTROLTYPE_BOOLEAN | A control with a boolean value. The value is an integer that is either 0 (FALSE) or non-zero (TRUE). |
MIXERCONTROL_CONTROLTYPE_BUTTON | A control whose value is 1 when the button is pressed (ie, some feature/action is enabled), or 0 if not pressed (ie, no action is taken). For example, this type of control may be used by a talkback button or pedal sustain -- the action/feature is on only while the button is pressed, and otherwise is not applicable. |
MIXERCONTROL_CONTROLTYPE_LOUDNESS | A control whose value is 1 to boost bass frequencies, or 0 for normal (ie, no boost). (A MIXERCONTROL_CONTROLTYPE_BASS fader control may be used to set the actual amount of boost, in conjunction with this control turning the boost on/off) |
MIXERCONTROL_CONTROLTYPE_MONO | A control whose value is 1 for mono operation (ie, all channels are summed into one), or 0 for normal (ie, stereo or multi-channel). |
MIXERCONTROL_CONTROLTYPE_MUTE | A control whose value is 1 to mute some feature, or 0 for normal (ie, no mute). |
MIXERCONTROL_CONTROLTYPE_ONOFF | A control whose value is 1 to enable some feature/action, or 0 to disable that feature/action. The difference between this and MIXERCONTROL_CONTROLTYPE_BUTTON is that the latter's 0 value doesn't disable the feature/action per se, but rather, simply represents a "not applicable" state for the feature/action. MIXERCONTROL_CONTROLTYPE_ONOFF would be similiar to a real on/off switch (ie, a checkmark button in Windows parlance) whereas MIXERCONTROL_CONTROLTYPE_BUTTON would be more akin to a "momentary switch" (ie, a pushbutton). The difference between MIXERCONTROL_CONTROLTYPE_ONOFF and MIXERCONTROL_CONTROLTYPE_BOOLEAN is merely the labeling/grahics that would be shown on the user interface. The former is "ON" when its value is 1. The latter is "TRUE" when its value is 1. So typically, the two buttons would be represented by different labels and/or graphics to reflect that difference in semantics. |
MIXERCONTROL_CONTROLTYPE_STEREOENH | A control whose value is 1 to enable a stereo enhance feature (ie, increase stereo separation), or 0 for normal (ie, no enhance). |
种类MIXERCONTROL_CT_CLASS_LIST 有以下4个类型与其关联:
MIXERCONTROL_CONTROLTYPE_SINGLESELECT | Allows one selection out of a choice of many selections. For example, this can be used to select a reverb type out of choice of many types (ie, Hall, Plate, Room, etc). |
MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT | Like MIXERCONTROL_CONTROLTYPE_SINGLESELECT, but allows the selection of more than one item simultaneously. |
MIXERCONTROL_CONTROLTYPE_MUX | Allows the selection of one audio line out of several choices of audio lines. For example, to allow a source line to be routed to one of several possible destination lines -- this control could list all of the possible choices of destination lines, and allow one to be chosen. |
MIXERCONTROL_CONTROLTYPE_MIXER | Like MIXERCONTROL_CONTROLTYPE_MUX, but allows the selection of more than one audio line simultaneously. For example, this control could be for a reverb component which allows several source lines to be simultaneously routed to it, and this control determines which source lines are selected for routing to the reverb. |
种类MIXERCONTROL_CT_CLASS_METER有以下4个类型与其关联:
MIXERCONTROL_CONTROLTYPE_BOOLEANMETER | A meter whose integer value is either 0 (ie, FALSE) or non-zero (TRUE). A MIXERCONTROLDETAILS_BOOLEAN struct is used to set/retrieve its value. |
MIXERCONTROL_CONTROLTYPE_PEAKMETER | A control with a value that is an integer whose allowable, maximum range is -32,768 (lowest) through 32,767 (highest). In other words, its value is a SHORT. A MIXERCONTROLDETAILS_SIGNED struct is used to set/retrieve its value. |
MIXERCONTROL_CONTROLTYPE_SIGNEDMETER | A control with a value that is an integer whose allowable, maximum range is -2,147,483,648 (lowest) through 2,147,483,647 (highest) inclusive. In other words, its value is a LONG. A MIXERCONTROLDETAILS_SIGNED struct is used to set/retrieve its value. |
MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER | Like MIXERCONTROL_CONTROLTYPE_SIGNEDMETER, but its allowable, maximum value range is from 0 (lowest) to 4,294,967,295. In other words, its value is a ULONG. A MIXERCONTROLDETAILS_UNSIGNED struct is used to set/retrieve its value. |
种类MIXERCONTROL_CT_CLASS_NUMBER关联的部分类型与种类MIXERCONTROL_CT_CLASS_METER关联的部分类型相似。
对于种类为MIXERCONTROL_CT_CLASS_NUMBER的控件, 你可以提供一个 Edit控件供用户输入一个数值。而对于种类为MIXERCONTROL_CT_CLASS_METER的控件,你可以提供一个类似于某种声音仪表的控件。
种类MIXERCONTROL_CT_CLASS_NUMBER有以下4个类型与其关联:
MIXERCONTROL_CONTROLTYPE_SIGNED | A control with a value that is an integer whose allowable, maximum range is -2,147,483,648 (lowest) through 2,147,483,647 (highest) inclusive. In other words, its value is a LONG. A MIXERCONTROLDETAILS_SIGNED struct is used to set/retrieve its value. |
MIXERCONTROL_CONTROLTYPE_UNSIGNED | Like MIXERCONTROL_CONTROLTYPE_SIGNEDMETER, but its allowable, maximum value range is from 0 (lowest) to 4,294,967,295. In other words, its value is a ULONG. A MIXERCONTROLDETAILS_UNSIGNED struct is used to set/retrieve its value. |
MIXERCONTROL_CONTROLTYPE_PERCENT | A control whose integer value is a percent. A MIXERCONTROLDETAILS_UNSIGNED struct is used to set/retrieve its value. |
MIXERCONTROL_CONTROLTYPE_DECIBELS | A control with a value that is an integer whose allowable, maximum range is -32,768 (lowest) through 32,767 (highest). In other words, its value is a SHORT. Each increment is a tenth of a decibel. A MIXERCONTROLDETAILS_SIGNED struct is used to set/retrieve its value. |
种类MIXERCONTROL_CT_CLASS_SLIDER有以下3个类型与其关联:
MIXERCONTROL_CONTROLTYPE_SLIDER | A slider with a value that is an integer whose allowable, maximum range is -32,768 (lowest) through 32,767 (highest). In other words, its value is a SHORT. |
MIXERCONTROL_CONTROLTYPE_PAN | A slider with a value that is an integer whose allowable, maximum range is -32,768 (far left) through 32,767 (far right). In other words, its value is a SHORT. It represents pan position in the stereo spectrum, where 0 is center position. |
MIXERCONTROL_CONTROLTYPE_QSOUNDPAN | A slider with a value that is an integer whose allowable, maximum range is -15 (lowest) through 15 (highest). In other words, its value is a SHORT. It represents Qsound's expanded sound setting. |
种类MIXERCONTROL_CT_CLASS_TIME有以下2个类型与其关联:
MIXERCONTROL_CONTROLTYPE_MICROTIME | A control with a value that is an integer whose allowable, maximum range is 0 (lowest) through 4,294,967,295. In other words, its value is a ULONG. Its value represents an amount of time in microseconds. |
MIXERCONTROL_CONTROLTYPE_MILLITIME | A control with a value that is an integer whose allowable, maximum range is 0 (lowest) through 4,294,967,295. In other words, its value is a ULONG. Its value represents an amount of time in milliseconds. |
值为MIXERCONTROL_CT_CLASS_CUSTOM的种类是一个自定义的种类。使用此种类控件的混音器只能由专门为这个混音器所写的程序识别此种类的控件有哪些类型,和使用什么样的结构去存取他们的值。(或许只有自定义的结构才能用)
MIXERLINE 结构、枚举线路
如果你不知道一个混音器有哪些线路(注:你不知道它所拥有的线路的类型,也不知道线路ID),有一个方法可以获取混音器线路的信息:首先调用mixerGetDevCaps()将混音器设备信息填入到MIXERCAPS结构体中。使用此结构体中的信息,你可以确定声卡上有多少个目标线路。然后,你就可以枚举每条目标线路和与每条目标线路关联的源线路。等你枚举完所有线路后,就可以枚举每个线路所关联的控件。
我们现在来检测一下此方法并学习一下和此Mixer API相关的结构体。
为了更好的理解Mixer API,我们应该看一下样例声卡内部结构,并看一下用于Mixer API的一些结构体。我们假设这个混音器设备使用C语言编写,因此使用C语言的结构体。
如前所述,Mixer API mixerGetDevCaps()可用来获取混音器设备信息。它会填充一个MIXERCAPS结构体。特别重要的是,MIXERCAPS的域成员cDestinations会告诉你此声卡上有多少个目标线路。但是它不会告诉你总共有多少个线路(注:目标线路和源线路的总数)。它仅仅指目标线路。你应该还记得,我们的样例声卡有2个目标线路。在这篇教程中,我们还会用任意值填充其它域,诸如混音器名称和ID。假设这是第一个安装到系统的混音器(注:ID=0)。如下是我们的混音器设备的MIXERCAPS结构体(在MMSYSTEM.H中定义)。
MIXERCAPS mixercaps=
{
0, /* manufacturer id */
0, /* product id */
0x0100, /* driver version #.Note that the high 8-bits are the version, and low 8-bits are revision. So, ourdriver version is 1.0 */
"ExampleSound Card", /* product name */
0, /* Support bits. None are currently defined */
2, /* # of destination lines */
};
如下是一个样例程序,其将传递一个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中定义)。我们假设SpeakerOut目标线路有两个控件——一个音量滑动条(控制流向SpeakerOut线路的总音量)和一个静音开关(控制Speaker Out线路的静音)。如下是我们的Speaker Out目标线路的MIXERLINE 结构体:
MIXERLINE mixerline_SpkrOut=
{
sizeof(MIXERLINE), /* size of MIXERLINE structure */
0, /* zero based index of destination line */
0, /* zero based source index (used only if
thisis a source line) */
0xFFFF0000, /* unique ID # for this line */
MIXERLINE_LINEF_ACTIVE, /* state/informationabout line */
0, /* driver specific information */
MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, /* component type */
2, /* # of channels this line supports */
4, /* # of source lines connected (used
only if this is a destination line)*/
2, /* # of controls in this line */
"SpkrOut", /* Short name for this line */
SpeakerOut, /* Long name for this line */
MIXERLINE_TARGETTYPE_WAVEOUT, /* MIXERLINE_TARGETTYPE_xxxx */
/* The followinginfo is just some info about the Mixer Device for this
line, prepended to the above info. It's mostly just for reference. */
0, /* device ID of our Mixer */
0, /* manufacturer id */
0, /* product id */
0x0100, /* driver version # */
"Example Sound Card", /* product name */
};
这里有一些要注意的地方。首先,SpeakerOut目标线路的域成员dwComponentType是目标线路类型——MIXERLINE_COMPONENTTYPE_DST_SPEAKERS。此类型值恰当的说明了这是一个Speaker Out目标线路。我已经选择了一个值0xFFFF000赋给dwLineID域。编程人员可以为其赋予任何值给这个域,但是其它任何线路的dwLineID域的值不能和此值相同。同样需要注意的是,既然我们的speaker output是立体声的,因此cChannels域值就是2。你应该还记得,这里有4个源线路连接到我们的Speaker Out目标线路,因此cConnections域的值是4。Name域是以null结尾的字符串。它可以是编程人员选择的任何值,shorter name域是用于标识那些紧挨存放的图形控件的。fdwLine域中设置有MIXERLINE_LINE_ACTIVE标志,这意味着此线路没有被禁用。
dwDestination域是一个从0开始的索引值,混音器中的第一个目标线路的索引值为0(如上示例程序所示)。第二个目标线路的索引值是1,第三个目标线路的索引值为2,等等。这和windows枚举混音器设备的概念一样(注:第一个安装的混音器的ID为0)。索引值和线路的ID并不一定相等(你可以从上面的示例程序中看出)。使用索引值的目的是什么?我们为什么要同时使用索引值和ID?或许有可以猜到,索引值主要在你需要枚举混音器的线路时使用。直到你枚举了所有的线路(注:获取每个线路的信息)之后,你才直到每个线路的ID。因此,你需要将这些索引值用于Mixer API函数中,去枚举所有的线路。但是一旦你已经取得了某个线路的信息,因此而知道了它的ID,然后你就可以更直接的更改它的设置。因此,索引值在起初枚举线路和控件以获取其ID及类型时很有用。在随后的控制线路和控件的过程中ID就起主要作用。
现在我们来看一下我们的目标线路ADC Wave Input的MIXERLINE结构。我们假设它有两个控件——一个音量控件和一个静音控件。如下是目标线路ADC Wave Input的MIXERLINE的结构:
MIXERLINEmixerline_WaveIn =
{
sizeof(MIXERLINE), /* size of MIXERLINE structure */
1, /* zero based index of destination line */
0, /* zero based source index (used only if
thisis a source line) */
0xFFFF0001, /* unique ID # for this line */
MIXERLINE_LINEF_ACTIVE, /* state/informationabout line */
0, /* driver specific information */
MIXERLINE_COMPONENTTYPE_DST_WAVEIN, /* component type */
2, /* # of channelsthis line supports */
2, /* # of source lines connected (used
only if this is a destination line) */
2, /* # of controls in this line */
"WaveIn", /* Short name for this line */
"WaveInput", /* Long name for this line */
MIXERLINE_TARGETTYPE_WAVEIN, /* MIXERLINE_TARGETTYPE_xxxx */
/* The followinginfo is just some info about the Mixer Device for this
line, prepended to the above info. It's mostly just for reference and is onlyvalid if the
Target Type field is not MIXERLINE_TARGETTYPE_UNDEFINED. */
0, 0, 0, 0x0100, "Example Sound Card",
};
注意目标线路ADC WaveInput的域dwComponentType的值
是MIXERLINE_COMPONENTTYPE_DST_WAVEIN,这表明它是一个波形输入组件。同样的,我选择了值0xFFFF0001赋予域dwLineID —— 和目标线路Speaker Out的域dwLineID值不同的值。
因为我们的声卡还具有数字化功能,因此cChannels域值是2。你应该还记得,有2个源线路连接到我们的目标线路ADC Wave Input,因此域cConnections是2。最后,注意dwDestination域的值是1,因为这是混音器中第二个目标线路。
Mixer API mixerGetLineInfo()为指定的线路填充MIXERLINE结构。这就是你获取某个线路信息的方法。如果你不知道一个线路的ID(在你第一次枚举线路的情况下),那么你可以通过索引值来引用它。通过给mixerGetLineInfo()传递值
MIXER_GETLINEINFOF_DESTINATION来表明你想通过线路的索引值来引用线路。如下展示了怎样获取我们的样例混音器中第一个目标线路的信息。在调用mixerGetLineInfo()和传递MIXERLINE结构体之前,你必须初始化两个域。cbStruct域的值必须设置为你传递的MIXERLINE结构所占的字节数,dwDestination域值必须设置为你想获取信息的线路的索引值。
记住第一个目标线路的索引值为0,因此如果要获取它的信息,我们就将dwDestination设置为0。
MIXERLINE mixerline;
MMRESULT err;
/* Get info aboutthe first destination line by its index */
mixerline.cbStruct =sizeof(MIXERLINE);
mixerline.dwDestination= 0;
if ((err =mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerline,MIXER_GETLINEINFOF_DESTINATION)))
{ /* An error */
printf("Error #%d calling mixerGetLineInfo()/n", err);
}
When the above call returns, mixerGetLineInfo() will havefilled in our MIXERLINE struct as per the Speaker Out (mixerline_SpkrOut)MIXERLINE struct shown above. (After all, the Speaker Out is the first line inour example Mixer -- it has an index of 0).
Now, if you want to fetch info about the seconddestination line in the mixer, the only thing different is the index value youstuff into the dwDestination field, as so:
/* Get info aboutthe second destination line by its index */
mixerLine.cbStruct =sizeof(MIXERLINE);
mixerLine.dwDestination= 1;
if ((err =mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerLine,MIXER_GETLINEINFOF_DESTINATION)))
{
/* An error */
printf("Error #%d calling mixerGetLineInfo()/n", err);
}
当上述调用返回之后,mixerGetLineInfo()函数将会使用如上所示的ADC Wave Input(mixerline_WaveIn)结构的值填充MIXERLINE结构(毕竟,ADC WaveInput是我们的样例混音器中的第二个线路——它的索引值为1)。
现在,你可以看到怎样通过每个线路的索引值来枚举所有的线路。如下是一个示例程序,将会打印出所有目标线路的名称。
MIXERCAPS mixcaps;
MIXERLINE mixerline;
MMRESULT err;
unsigned long i;
/* Get info aboutthe first Mixer Device */
if (!(err =mixerGetDevCaps((UINT)mixerHandle, &mixcaps, sizeof(MIXERCAPS))))
{
/* Print out thename of each destination line */
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,如此等等。记住Speaker Out目标线路有4个与其关联的源线路:“InternalCD Audio”、“Synth”、“DAC Wave Out”、和“Microphone Input”。因此它们的索引值分别是0、1、2、3。我们看一下这4个源线路的MIXERLINE结构体,假定他们都有两个控件 —— 一个音量控件和一个静音控件。同样地,假定每个都是立体声控件。
MIXERLINEmixerline_CD =
{
sizeof(MIXERLINE),
0, /* zero based index of destination line */
0, /* zero based source index */
0x00000000, /* unique ID # for this line */
MIXERLINE_LINEF_ACTIVE|MIXERLINE_LINEF_SOURCE, /* state/information about line*/
0,
MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC, /* component type */
2, /* # of channels this line supports */
0, /* Not applicable for source lines */
2, /* # of controls in this line */
"CDAudio", /* Short name for this line */
"Internal CDAudio", /* Long name for this line */
MIXERLINE_TARGETTYPE_UNDEFINED, /* MIXERLINE_TARGETTYPE_xxxx */
0, 0, 0, 0x0100, "Example Sound Card",
};
MIXERLINEmixerline_Synth = {
sizeof(MIXERLINE),
0, /* zero based index ofdestination line */
1, /* zero based source index */
0x00000001, /* unique ID # for this line */
MIXERLINE_LINEF_ACTIVE|MIXERLINE_LINEF_SOURCE,
0,
MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER, /* component type */
2, /* # of channels this line supports */
0, /* Not applicable for source lines */
2, /* # of controls in this line */
"Synth", /* Short name for this line */
"Synth", /* Long name for this line */
MIXERLINE_TARGETTYPE_UNDEFINED, /* MIXERLINE_TARGETTYPE_xxxx */
0, 0, 0, 0x0100, "Example Sound Card",
};
MIXERLINEmixerline_WaveOut = {
sizeof(MIXERLINE),
0, /* zero based index of destination line */
2, /* zero based source index */
0x00000002, /* unique ID # for this line */
MIXERLINE_LINEF_ACTIVE|MIXERLINE_LINEF_SOURCE,
0,
MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT, /* component type */
2, /* # of channels this line supports */
0, /* Not applicable for source lines */
2, /* # of controls in this line */
"WaveOut", /* Short name for this line */
"DAC WaveOut", /* Long name for this line */
MIXERLINE_TARGETTYPE_WAVEOUT, /* MIXERLINE_TARGETTYPE_xxxx */
0, 0, 0, 0x0100, "Example Sound Card",
};
MIXERLINEmixerline_Mic = {
sizeof(MIXERLINE),
0, /* zero based index of destination line */
3, /* zero based source index */
0x00000003, /* unique ID # for this line */
MIXERLINE_LINEF_ACTIVE|MIXERLINE_LINEF_SOURCE,
0,
MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, /* component type */
2, /* # of channels this linesupports */
0, /* Not applicable for source lines */
2, /* # of controls in this line */
"Mic", /* Short name for this line */
"MicrophoneInput", /* Long name for this line */
MIXERLINE_TARGETTYPE_WAVEIN, /* MIXERLINE_TARGETTYPE_xxxx */
0, 0, 0, 0x0100, "Example Sound Card",
};
你必须注意的一件事是:不同于目标线路,源线路的MIXERLINE中设置有
MIXERLINE_LINE_SOURCE标志。当有这个标志时,你就知道你获取的是一个源线路的信息。其次,注意这个目标线路的索引值是0。这是因为以上所有源线路连接的目标线路Speaker Out是我们的混音器中的第一个线路(因此它的索引值为0)。再次,注意以上4个源线路的索引值分别是0、1、2和3。最后,注意每个线路的ID都是唯一的——不像其它的线路,包括所有的目标线路。
MixerAPI mixerGetLineInfo()同样会为源线路填充MIXERLINE结构体。同样的,你可以通过索引值来引用源线路,但是你必须知道每个目标线路的索引值。通过传递MIXER_GETLINEINFOF_SOURCE 标志给mixerGetLineInfo()函数,告知它你要通过索引值来引用源线路。如下是怎样获取我们的样例混音器中的第一个源线路(目标线路为SpeakerOut)的样例程序。在调用mixerGetLineInfo()和传递MIXERLINE结构体之前,你必须初始化3个域:cbStruct域的值必须设置为你传递的MIXERLINE结构所占的字节数,dwSource域必须设置为你想获取信息的源线路的索引值,dwDestination域必须设置为和这个源线路相连的目标线路的索引值。
MIXERLINE mixerline;
MMRESULT err;
/* Get info about the first source line (of the first destination line) byits index*/
mixerline.cbStruct = sizeof(MIXERLINE);
mixerline.dwDestination = 0;
mixerline.dwSource = 0;
if ((err = mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerline,MIXER_GETLINEINFOF_SOURCE)))
{/* An error */
printf("Error #%d callingmixerGetLineInfo()/n", err);
}
当上述调用返回后,mixerGetLineInfo()将会按照如上所示的"Internal CD Audio"
(mixerline_CD)结构的值来填充MIXERLINE结构体,你可以从MIXERLINE的dwLineID域获取它的线路ID。
现在,如果你想获取目标线路SpeakerOut的第二条源线路的信息,唯一不同的就是给dwSource域填入一个不同的索引值,如下:
/* Get info about the second source line by its index */
mixerline.cbStruct = sizeof(MIXERLINE);
mixerline.dwDestination = 0;
mixerline.dwSource = 1;
if ((err = mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerline,MIXER_GETLINEINFOF_SOURCE)))
{/* An error */
printf("Error #%d callingmixerGetLineInfo()/n", err);
}
当上述调用返回之后,mixerGetLineInfo()将会按照前面所示的"Synth" (mixerline_Synth)结构的值来填充MIXERLINE结构体。
现在,你应该知道怎样通过源线路(属于某个目标线路)的索引值来枚举的所有源线路了。这里是一个示例程序,它输出了我们的样例混音器中所有目标线路的以及其各自关联的所有源线路的名称。
MIXERCAPS mixcaps;
MIXERLINE mixerline;
MMRESULT err;
unsigned long i, n, numSrc;
/* Get info about the first Mixer Device */
if (!(err = mixerGetDevCaps((UINT)mixerHandle, &mixcaps,sizeof(MIXERCAPS))))
{
/* Print out the name of each destination line */
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);
/*Print out the name of each source line in this destination */
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结构体的dwLine域,然后传递MIXER_GETLINEINFOF_LINEID参数,按如下方式调用mixerGetLineInfo():
/* Get info about the "Microphone Input" source line by its ID*/
mixerline.cbStruct = sizeof(MIXERLINE);
mixerline.dwLineID = 0x00000003; /* The ID for "MicrophoneInput" */
if ((err = mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerline,MIXER_GETLINEINFOF_LINEID)))
{/* An error */
printf("Error #%d callingmixerGetLineInfo()/n", err);
}
以上方法均适用于源线路和目标线路。一旦你知道了一个线路的ID,你就可以根据它直接获取线路的信息,根本不需要知道索引值。
通过类型获取线路信息
通常情况下,你不需要知道混音器中所有的线路。或许在你的程序中你仅仅和某个类型的线路打交道。比如,假设你正在编写一个MIDI文件播放器。现在,我们的样例声卡的某些组件对于你来说毫无用处。MIDI不是数字音频数据,因此"DAC Wave In"组件(以及所有连接到它的所有源线路)对你来说毫无意义。同样,目标线路SpeakerOut 的源线路 "InternalCD Audio"、"DAC Wave Out" 和"Microphone Input"对你来说也毫无意义。我们的样例声卡上唯一一个可以处理MIDI数据回放的组件是目标线路Speaker Out的"Synth"源线路。正是这个线路的控件影响着MIDI数据的回放。
较之于枚举混音器中的所有线路直到碰到一个类型为
MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER的线路的方法相比,
mixerGetLineInfo()让你直接获取指定类型的线路信息的方法更胜一筹。你只需要将MIXERLINE的dwComponentType域指定为你想要线路类型,然后指定
MIXER_GETLINEINFOF_COMPONENTTYPE并按如下方式调用mixerGetLineInfo():
/* Get info about a "Synth" type of source line */
mixerline.cbStruct = sizeof(MIXERLINE);
mixerline.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER; /* Wewant a Synth type */
if ((err = mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerline,MIXER_GETLINEINFOF_COMPONENTTYPE)))
{
/* An error */
printf("Error #%d callingmixerGetLineInfo()/n", err);
}
系统将会用混音器中第一个类型为MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER的线路的信息填充此MIXERLINE结构体。(如果混音器中没有此类型的线路,将会返回一个MIXERR_INVALLINE的错误)。一旦获取了此线路的信息,你就可以操纵它的控件了。这样当你有特殊的需求时就省去了通过枚举得到某个线路的麻烦。
MIXERCONTROL结构、枚举控件
你可以通过Mixer APImixerGetLineControls()来获取线路中控件的信息。这个API将会将某个控件的信息填入到一个MIXERCONTROL结构体中。
我们首先来看一下MIXERCONTROL结构体。如前所述,我们的Speaker Out目标线路有两个控件:一个音量滑动器和一个静音开关。每个控件都有一个MIXERCONTROL结构体与其相连。我们首先看一下这些控件的MIXERCONTROL结构:
MIXERCONTROL mixerctl_Spkr_Vol =
{
sizeof(MIXERCONTROL), /*size of a MIXERCONTROL */
0x00000000, /*unique ID # for this control */
MIXERCONTROL_CONTROLTYPE_VOLUME, /* type ofcontrol */
MIXERCONTROL_CONTROLF_UNIFORM, /*flag bits */
0, /*# of items per channels (used only if the
MIXERCONTROL_CONTROLF_MULTIPLEflag bit is also set) */
"Volume", /*Short name for this control */
"Speaker OutVolume", /*Long name for this control */
0, /*Minimum value to which this control can be set */
65535, /*Maximum value to which this control can be set */
0, 0, 0,0, /*These fields are reserved for future use */
31, /*Step amount for the value */
0, 0, 0, 0,0, /*These fields are reserved for future use */
};
MIXERCONTROL mixerctl_Spkr_Mute = {
sizeof(MIXERCONTROL),
0x00000001, /*unique ID # for this control */
MIXERCONTROL_CONTROLTYPE_MUTE, /*type of control */
MIXERCONTROL_CONTROLF_UNIFORM,
0,
"Mute", /*Short name for this control */
"Speaker OutMute", /*Long name for this control */
0, /*Minimum value to which this control can be set */
1, /*Maximum value to which this control can be set */
0, 0, 0, 0,
0, /*Step amount for the value */
0, 0, 0, 0, 0,
};
上面有几个问题要提及一下。首先,注意每个控件都有一个唯一的ID,这些ID可以和线路的ID相同(比如控件mixerctl_Spkr_Vol的ID恰巧和线路mixerline_CD的ID相同)。但是每个控件的ID都不能和其它控件的ID相同,包括同其他线路的控件。(例如,线路Speaker Out的音量滑动器控件的ID不能和线路静音开关"ADC Wave In"的任何一个控件的ID相同)。
我已设置了MIXERCONTROL_CONTROLF_UNIFORM标志,这个标志的意思是:虽然SpeakerOut是立体声的(注:有两个声道),但是并不是每个控件都有一个单独的音量控件。(这里没有左右声道各自的音量设置)。对于这两个声道只有一个共同的音量设置。因此这两个声道总是被设置为相同的音量(后面我们会学非均衡(not uniform)的控件)。
同样需要注意的是每个控件都有一个适当的类型。
音量滑动器控件的类型为MIXERCONTROL_CONTROLTYPE_VOLUME,
静音开关控件的类型为MIXERCONTROL_CONTROLTYPE_MUTE。
MIXERCONTROL还会告诉你这个控件的最大值和最小值。例如,音量滑动器控件可以设置0到65535之前的任何一个值。其中0是最小值(注:此时音量最小),65535是最大值(注:此时音量最大),这是不是意味着音量滑动器控件有65535个离散的等级呢?(注:可以被设置为从0至65535之间(包括0和65535)的任何值?)。不一定。你还得看一下等级总数这个域。它会告诉你这个控件有多少个有效的等级。在目前情况下,我们有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的结构。
例如,我们知道我们的Speaker Out目标线路有两个控件:一个音量滑动器控件和一个静音开关(记住MIXERLINE结构的cControls域是2)。因此,如果要获取这两个控件的信息,我们就必须给mixerGetLineControls传递包含两个MIXERCONTROL结构的数组。同时我们必须传递MIXER_GETLINECONTROLSF_ALL标志表明我们需要获取所有控件的信息。为了告诉mixerGetLineControls()我们希望获取哪个线路的控件的信息,我们还必须初始化并传递一个类型为MIXERLINECONTROLS的结构。如下是获取线路Speaker Out的控件信息的样例程序:
/* Let's just declare an array of 2 MIXERCONTROL structs since
we know that the Speaker Out has 2 controls. For a realprogram,
you typically won't know ahead of time how big an arrayyou'll need,
and therefore would instead allocate an appropriatelysized array */
MIXERCONTROL mixerControlArray[2];
MIXERLINECONTROLS mixerLineControls;
MMRESULT err;
mixerLineControls.cbStruct = sizeof(MIXERLINECONTROLS);
/* The Speaker Out line has a total of 2 controls. And
that's how many we want to retrieve info for here */
mixerLineControls.cControls = 2;
/* Tell mixerGetLineControls() for which line we're retrieving info.
We do this by putting the desired line's ID number indwLineID.
The Speaker Out line has an ID of 0xFFFF0000 */
mixerLineControls.dwLineID = 0xFFFF0000;
/* Give mixerGetLineControls() the address of our array of
MIXERCONTROL structs big enough to hold info on allcontrols */
mixerLineControls.pamxctrl = &mixerControlArray[0];
/* Tell mixerGetLineControls() how big each MIXERCONTROL is. This
saves having to initialize the cbStruct of eachindividual
MIXERCONTROL in the array */
mixerLineControls.cbmxctrl = sizeof(MIXERCONTROL);
/* Retrieve info on all controls for this line simultaneously */
if ((err = mixerGetLineControls((HMIXEROBJ)mixerHandle,&mixerLineControls, MIXER_GETLINECONTROLSF_ALL)))
{
/* An error */
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)和所有控件总数(不包括总数)之间个控件的信息是不行的。例如,假设我们的SpeakerOut线路有5个控件(而不是目前的2个),你不能仅仅获取前3个控件。例如,你只能一次获取5个控件中1个控件的信息,或者一次获取着5个控件的信息。(注:也就是要么一次获取一个控件的信息,要么一次获取所有的信息)。
通过控件ID获取控件信息
一旦你知道了一个控件的ID(你可以使用上面所示的方法,首先枚举所有控件,然后从MIXERCONTROL的dwControlID域获取控件的ID),你就可以通过这个ID仅仅获取这个控件的信息。你甚至不必知道这个控件所属的线路的ID。你也不必同时获取所有控件的信息。你仅仅只需要初始化MIXERCONTROL的dwControlID域,然后指定MIXER_GETLINECONTROLSF_ONEBYID标志并按如下方式调用mixerGetLineControls():
/* We need only 1 MIXERCONTROL struct since
we're fetching info for only 1 control */
MIXERCONTROL mixerControlArray;
MIXERLINECONTROLS mixerLineControls;
MMRESULT err;
mixerLineControls.cbStruct = sizeof(MIXERLINECONTROLS);
/* We want to fetch info on only 1 control */
mixerLineControls.cControls = 1;
/* Tell mixerGetLineControls() for which control we're retrieving
info. We do this by putting the desired control's IDnumber in
dwControlID. The Speaker Out line's volume slider has anID
of 0x00000000 */
mixerLineControls.dwControlID = 0x00000000;
/* Give mixerGetLineControls() the address of the
MIXERCONTROL struct to hold info */
mixerLineControls.pamxctrl = &mixerControlArray;
/* Tell mixerGetLineControls() how big the MIXERCONTROL is. This
saves having to initialize the cbStruct of theMIXERCONTROL itself */
mixerLineControls.cbmxctrl = sizeof(MIXERCONTROL);
/* Retrieve info on only the volume slider control for the Speaker Outline */
if ((err = mixerGetLineControls((HMIXEROBJ)mixerHandle,&mixerLineControls, MIXER_GETLINECONTROLSF_ONEBYID)))
{
/* An error */
printf("Error #%d callingmixerGetLineControls()/n", err);
}
通过控件类型获取控件信息
通常情况下,你不必知道某个线路中所有控件的信息。或许在你的程序中你仅仅和某个类型的控件打交道。比如,假设你在写一个简单的MIDI文件回放程序,你给终端用户提供的仅仅是一个音量滑动器“Synth”。前面我们已经知道怎样通过组件的类型获取MIDI回放线路并获取此线路的信息,比如获取此线路的ID。你可以用这个线路ID来搜索此线路中某个特定类型的控件,例如,你可以寻找一个类型为MIXERCONTROL_CONTROLTYPE_VOLUME的控件。
因此,较之于通过枚举线路中所有控件直到你碰到一个类型为
MIXERCONTROL_CONTROLTYPE_VOLUME的控件的方法,mixerGetLineControls()提供的直接通过类型获取控件的方式更胜一筹。你仅仅只需要将MIXERCONTROLS的域dwControlType设定为你想要的类型,然后指定MIXER_GETLINECONTROLSF_ONEBYTYPE标志并按如下方式调用mixerGetLineControls():(假设你已经获取了线路”Synth”的ID,并存储在”SynthID”变量中)
MIXERCONTROL mixerControlArray;
MIXERLINECONTROLS mixerLineControls;
MMRESULT err;
mixerLineControls.cbStruct = sizeof(MIXERLINECONTROLS);
/* Tell mixerGetLineControls() for which line we're retrieving info.
We do this by putting the desired line's ID number indwLineID */
mixerLineControls.dwLineID = SynthID;
/* We want to fetch info on only 1 control */
mixerLineControls.cControls = 1;
/* Tell mixerGetLineControls() for which type of control we're
retrieving info. We do this by putting the desiredcontrol type
in dwControlType */
mixerLineControls.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
/* Give mixerGetLineControls() the address of the MIXERCONTROL
struct to hold info */
mixerLineControls.pamxctrl = &mixerControlArray;
/* Tell mixerGetLineControls() how big the MIXERCONTROL is. This
saves having to initialize the cbStruct of theMIXERCONTROL itself */
mixerLineControls.cbmxctrl = sizeof(MIXERCONTROL);
/* Retrieve info on only any volume slider control for this line */
if ((err= mixerGetLineControls((HMIXEROBJ)mixerHandle, &mixerLineControls,MIXER_GETLINECONTROLSF_ONEBYTYPE)))
{
/* An error */
printf("Error #%d callingmixerGetLineControls()/n", err);
}
系统将会使用此线路中第一个拥有MIXERCONTROL_CONTROLTYPE_VOLUME类型的控件的信息来填充MIXERCONTROL结构体。(如果此线路中没有此类型的控件,将会返回一个错误值MIXERR_INVALCONTROL)。一旦你获取到了控件的信息,就可以直接操作此控件了。例如,你可以从MIXERCONTROL的域dwControlID获取控件的ID。这样,当你想要某个类型的控件时就不用枚举所有的控件了。
获取、设置某个控件的值
现在,我们讲述混音器的目标——获取一个控件的值(这样你就可以将其当前的值显示给终端用户),和设置一个控件的值(这样可以让终端用户调整控件的值)。
想要获取或设置某个控件的值,你必须知道此控件的ID。可以通过mixerGetControlDetails()获取其当前值,通过mixerSetControlDetails()设置为某个特定的值。这些函数都使用一个类型为MIXERCONTROLDETAILS的结构体。你可以初始化其中的部分域来告诉
mixerGetControlDetails()/mixerSetControlDetails()你想设置/获取哪个控件的值。你同时还得提供另外一个即将存放值的结构。
例如,我们获取线路Speaker Out的音量滑动器控件的当前值,到目前位置,我们已知道了怎样获取这个控件的信息(例如,这个控件的ID)。为了获取控件的值,我们需要提供一个特殊的结构体来存放返回值。
我们需要使用什么样的结构?哦,这要看控件的类型了。音量滑动器控件的类型为
MIXERCONTROL_CONTROLTYPE_VOLUME,如果你回头看一下chart aboutthe fader class of controls,就知道了这个值将使用一个类型为MIXERCONTROLDETAILS_UNSIGNED的结构。这个结构只有一个域dwValue,用于存放返回值。因此我们提供一个MIXERCONTROLDETAILS_UNSIGNED类型的结构给mixerGetControlDetails()(通过MIXERCONTROLDETAILS结构),如下是一个样例程序,获取并输出Speaker Out线路中音量滑动器控件的当前值。
/* We need a MIXERCONTROLDETAILS_UNSIGNED struct to retrieve the
value of a control whose type isMIXERCONTROL_CONTROLTYPE_VOLUME */
MIXERCONTROLDETAILS_UNSIGNED value;
MIXERCONTROLDETAILS mixerControlDetails;
MMRESULT err;
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
/* Tell mixerGetControlDetails() which control whose value we
want to retrieve. We do this by putting the desiredcontrol's
ID number in dwControlID. Remember that the Speaker Outline's
volume slider has an ID of 0x00000000 */
mixerControlDetails.dwControlID = 0x00000000;
/* This is always 1 for a MIXERCONTROL_CONTROLF_UNIFORM. control */
mixerControlDetails.cChannels = 1;
/* This is always 0 except for a MIXERCONTROL_CONTROLF_MULTIPLE control */
mixerControlDetails.cMultipleItems = 0;
/* Give mixerGetControlDetails() the address of the
MIXERCONTROLDETAILS_UNSIGNED struct into which to returnthe value */
mixerControlDetails.paDetails = &value;
/* Tell mixerGetControlDetails() how big the MIXERCONTROLDETAILS_UNSIGNEDis */
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
/* Retrieve the current value of the volume slider control for this line*/
if ((err= mixerGetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails,MIXER_GETCONTROLDETAILSF_VALUE)))
{
/* An error */
printf("Error #%d callingmixerGetControlDetails()/n", err);
}
else
{
printf("It's value is %lu/n",value.dwValue);
}
若要设置一个控件的值,你只需填充此结构,然后将其传递给mixerSetControlDetails()。你同时还要指定MIXER_SETCONTROLDETAILSF_VALUE结构。这里是一个样例程序,其将线路Speaker Out的音量滑动器控件的值设置为31。
MIXERCONTROLDETAILS_UNSIGNED value;
MIXERCONTROLDETAILS mixerControlDetails;
MMRESULT err;
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
/* Tell mixerSetControlDetails() which control whose value we
want to set. We do this by putting the desired control's
ID number in dwControlID. Remember that the Speaker Outline's
volume slider has an ID of 0x00000000 */
mixerControlDetails.dwControlID = 0x00000000;
/* This is always 1 for a MIXERCONTROL_CONTROLF_UNIFORM. control */
mixerControlDetails.cChannels = 1;
/* This is always 0 except for a MIXERCONTROL_CONTROLF_MULTIPLE control */
mixerControlDetails.cMultipleItems = 0;
/* Give mixerSetControlDetails() the address of the
MIXERCONTROLDETAILS_UNSIGNED struct into which we placethe value */
mixerControlDetails.paDetails = &value;
/* Tell mixerSetControlDetails() how big the MIXERCONTROLDETAILS_UNSIGNEDis */
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
/* Store the value */
value.dwValue = 31;
/* Set the value of the volume slider control for this line */
if ((err= mixerSetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails,MIXER_SETCONTROLDETAILSF_VALUE)))
{
/* An error */
printf("Error #%d callingmixerSetControlDetails()/n", err);
}
多声道控件
前面已说过,当将设置了控件的MIXERCONTROL_CONTROLF_UNIFORM标志时,所有声道都共享同一个值。例如,对于Speaker Out线路,它是立体声线路,但是其左右声道并没有独立的值。
但是,若一个控件的MIXERCONTROL_CONTROLF_UNIFORM值没有设置,并且此控件有一个以上的声道,那么每个声道都有一个独立的值。因为这个原因,当你设置/获取某个控件的值时,你必须提供多个特殊的结构。例如,假设Speaker Out线路的音量滑动器控件没有设置
MIXERCONTROL_CONTROLF_UNIFORM标志。因为此线路有两个声道,所以我们必须提供两个MIXERCONTROLDETAILS_UNSIGNED结构来存放获取/设置其左右声道的值,
我们需要使用一个类型为MIXERCONTROLDETAILS_UNSIGNED的数组。第一个MIXERCONTROLDETAILS_UNSIGNED结构将存放第一个声道的值,第二个结构将存放第二个声道的值。如下是一个样例程序,获取我们的Speaker Out线路的音量滑动器控件的左右声道的值:
/* We need 2 MIXERCONTROLDETAILS_UNSIGNED structs to retrieve the
values of a stereo control that is not MIXERCONTROL_CONTROLF_UNIFORM.*/
MIXERCONTROLDETAILS_UNSIGNED value[2];
MIXERCONTROLDETAILS mixerControlDetails;
MMRESULT err;
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
/* Tell mixerGetControlDetails() which control whose value we
want to retrieve. We do this by putting the desiredcontrol's
ID number in dwControlID. Remember that the Speaker Outline's
volume slider has an ID of 0x00000000 */
mixerControlDetails.dwControlID = 0x00000000;
/* We want to retrieve values for both channels */
mixerControlDetails.cChannels = 2;
/* This is always 0 except for a MIXERCONTROL_CONTROLF_MULTIPLE control */
mixerControlDetails.cMultipleItems = 0;
/* Give mixerGetControlDetails() the address of the
MIXERCONTROLDETAILS_UNSIGNED array into which to returnthe values */
mixerControlDetails.paDetails = &value[0];
/* Tell mixerGetControlDetails() how big each MIXERCONTROLDETAILS_UNSIGNEDis */
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
/* Retrieve the current values of both channels */
if ((err= mixerGetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails,MIXER_GETCONTROLDETAILSF_VALUE)))
{
/* An error */
printf("Error #%d callingmixerGetControlDetails()/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);
}
To set the values of both channels, you fill in the values of bothMIXERCONTROLDETAILS_UNSIGNED structs. Here is an example of setting the leftchannel's volume to 31 and the right channel's volume to 0.
/* We need 2 MIXERCONTROLDETAILS_UNSIGNED structs to set the
values of a stereo control that is notMIXERCONTROL_CONTROLF_UNIFORM. */
MIXERCONTROLDETAILS_UNSIGNED value[2];
MIXERCONTROLDETAILS mixerControlDetails;
MMRESULT err;
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
/* Tell mixerSetControlDetails() which control whose value we
want to set. We do this by putting the desired control's
ID number in dwControlID. Remember that the Speaker Outline's
volume slider has an ID of 0x00000000 */
mixerControlDetails.dwControlID = 0x00000000;
/* We want to set values for both channels */
mixerControlDetails.cChannels = 2;
/* This is always 0 except for a MIXERCONTROL_CONTROLF_MULTIPLE control */
mixerControlDetails.cMultipleItems = 0;
/* Give mixerSetControlDetails() the address of the
MIXERCONTROLDETAILS_UNSIGNED structs into which we placethe values */
mixerControlDetails.paDetails = &value[0];
/* Tell mixerSetControlDetails() how big each MIXERCONTROLDETAILS_UNSIGNEDis */
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
/* Store the left channel's value */
value[0].dwValue = 31;
/* Store the right channel's value */
value[1].dwValue = 0;
/* Set the left/right values of the volume slider control for this line */
if ((err= mixerSetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails,MIXER_SETCONTROLDETAILSF_VALUE)))
{
/* An error */
printf("Error #%d callingmixerSetControlDetails()/n", err);
}
当然,一个控件或许有2个以上的控件。对一个给定的控件,你必须提供足够大的结构来容纳所有的声道的值。因此,你必须根据需要来开辟数组空间。
一次存取一个控件中的某几个声道是非法的。例如,一个控件有8个声道,但是你只取其前2个声道的值,这是不允许的。你必须同时存取一个控件的所有声道才可以。但是这里有一条有关设置一个值的规则:如果你仅仅设置第一个声道的值,那么
mixerSetControlDetails()自动将控件设置MIXERCONTROL_CONTROLF_UNIFORM标志。最终结果是此控件的所有声道都被设置为这个值。因此,你可以通过仅仅设置第一个声道的值来达到将所有的声道设置为同一个值的目的。
多条目控件
你不会经常碰到“多条目”控件。一个多条目控件就是那种一个声道关联着多个值的控件。
下面是一个图形均衡器控件。让我们简单的学习一下这个样例。假设一个声卡中有以下3个图形均衡器。
<!--[if gte vml 1]> <![endif]--> |
这个控件有3个值与其关联—与“低通道”关联的值、与“中通道”关联的值、与“高通道”关联的值。(注:假设每个通道都能设置为不同的值,否则它就是一个无用的图形均衡器了)。这样表现出来的就是一个多条目控件,它有3个值与其关联。
我们再进一步假设这个控件属于Speaker Out线路。
我们来看一下MIXERCONTROL结构体。一个多条目控件的MIXERCONTROL的域dwControlType值会有MIXERCONTROL_CONTROLF_MULTIPLE位标志设置。MIXERCONTROL 的cMultipleItems域同时会告诉你每个声道有几个条目。
MIXERCONTROL mixerctl_EQ = {
sizeof(MIXERCONTROL), /* size of a MIXERCONTROL*/
0x00000002, /* unique ID # for thiscontrol */
MIXERCONTROL_CONTROLTYPE_EQUALIZER, /* type of control */
MIXERCONTROL_CONTROLF_UNIFORM.|MIXERCONTROL_CONTROLF_MULTIPLE, /* flag bits */
3, /* # of items per channel*/
"EQ", /* Short name for thiscontrol */
"GraphicEqualizer", /* Long name for thiscontrol */
0, /* Minimum value to whichthis control can be set */
65535, /* Maximum value to whichthis control can be set */
0, 0, 0,0, /* These fields arereserved for future use */
31, /* Step amount for thevalue */
0, 0, 0, 0,0, /* These fields arereserved for future use */
};
首先,注意控件ID不同于此混音器中其它控件的ID。同样要注意的是,还设置了MIXERCONTROL_CONTROLF_MULTIPLE位标志。cMultipleItems被设置为3,表明每个声道有3个条目。(但是我已将此控件设置了MIXERCONTROL_CONTROLF_UNIFORM标志,因此总共只有3个值,即使线路Speaker Out是立体声的。换句话说,每个通道的值同时影响着所有声道)
为了获取3个通道的值,我们需要一个包含3个结构的数组。需要什么类型的结构呢?哦,类型为MIXERCONTROL_CONTROLTYPE_EQUALIZER的控件的种类是fader,因此你会想起此种类的值都使用MIXERCONTROLDETAILS_UNSIGNED类型的结构。如下展示了怎样获取3个通道的值。
/* We need 3 MIXERCONTROLDETAILS_UNSIGNED structs to retrieve the
values of this control */
MIXERCONTROLDETAILS_UNSIGNED value[3];
MIXERCONTROLDETAILS mixerControlDetails;
MMRESULT err;
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
/* Tell mixerGetControlDetails() which control whose value we
want to retrieve */
mixerControlDetails.dwControlID = 0x00000002;
/* It's a MIXERCONTROL_CONTROLF_UNIFORM. control, so the values
for all channels are the same as the first */
mixerControlDetails.cChannels = 1;
/* There are 3 items per channel */
mixerControlDetails.cMultipleItems = 3;
/* Give mixerGetControlDetails() the address of the
MIXERCONTROLDETAILS_UNSIGNED array into which to returnthe values */
mixerControlDetails.paDetails = &value[0];
/* Tell mixerGetControlDetails() how big each MIXERCONTROLDETAILS_UNSIGNEDis */
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
/* Retrieve the current values of all 3 bands */
if ((err= mixerGetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails,MIXER_GETCONTROLDETAILSF_VALUE)))
{
/* An error */
printf("Error #%d callingmixerGetControlDetails()/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);
}
To set the values of all 3 bands, you fill in the values of theMIXERCONTROLDETAILS_UNSIGNED structs. Here is an example of setting the Lowband to 31, the Mid band to 0, and the High band to 62.
/* We need 3 MIXERCONTROLDETAILS_UNSIGNED structs to set the
values of the 3 bands */
MIXERCONTROLDETAILS_UNSIGNED value[3];
MIXERCONTROLDETAILS mixerControlDetails;
MMRESULT err;
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
/* Tell mixerSetControlDetails() which control whose values we
want to set */
mixerControlDetails.dwControlID = 0x00000002;
/* The values for all channels are the same as the first channel */
mixerControlDetails.cChannels = 1;
/* We're setting all 3 bands */
mixerControlDetails.cMultipleItems = 3;
/* Give mixerSetControlDetails() the address of the
MIXERCONTROLDETAILS_UNSIGNED structs into which we placethe values */
mixerControlDetails.paDetails = &value[0];
/* Tell mixerSetControlDetails() how big each MIXERCONTROLDETAILS_UNSIGNEDis */
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
/* Store the Low band's value */
value[0].dwValue = 31;
/* Store the Mid band's value */
value[1].dwValue = 0;
/* Store the High band's value */
value[2].dwValue = 62;
/* Set the values of the 3 bands for this control */
if ((err= mixerSetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails, MIXER_SETCONTROLDETAILSF_VALUE)))
{
/* An error */
printf("Error #%d callingmixerSetControlDetails()/n", err);
}
现在我们将控件的MIXERCONTROL_CONTROLF_UNIFORM标志去掉。因此,每个声道的每个条目都有其自己的值。因为Speaker Out
BsR7K/w有两个声道,那意味着我们需要总计为2 (声道) * 3 (条目)个结构,也就是6个值。我们的图形均衡器如下图所示:
<!--[if gte vml 1]> <![endif]--> | <!--[if gte vml 1]> <![endif]--> |
我们需要6个类型为MIXERCONTROLDETAILS_UNSIGNED的结构来存放所有声道的所有条目的值。哦,在前面的样例程序中,我都将这些条目的标签设置过了。如果你希望将他们输出,你就应该向混音器询问这些值。为了达到这个目标,你必须提供一个类型为MIXERCONTROLDETAILS_LISTTEXT结构的数组,就像你提供一组类型为MIXERCONTROLDETAILS_UNSIGNED的结构来获取所有声道的所有条目的值一样。
/* We need 6 MIXERCONTROLDETAILS_UNSIGNED structs to retrieve the
values of this control */
MIXERCONTROLDETAILS_UNSIGNED value[6];
/* We need 6 MIXERCONTROLDETAILS_LISTTEXT structs to retrieve the
labels of all items */
MIXERCONTROLDETAILS_LISTTEXT label[6];
MIXERCONTROLDETAILS mixerControlDetails;
MMRESULT err;
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
/* Tell mixerGetControlDetails() which control whose value we
want to retrieve */
mixerControlDetails.dwControlID = 0x00000002;
/* Our Speaker Out has 2 channels */
mixerControlDetails.cChannels = 2;
/* There are 3 items per channel */
mixerControlDetails.cMultipleItems = 3;
/* Give mixerGetControlDetails() the address of the
MIXERCONTROLDETAILS_UNSIGNED array into which to returnthe values */
mixerControlDetails.paDetails = &value[0];
/* Tell mixerGetControlDetails() how big each MIXERCONTROLDETAILS_UNSIGNEDis */
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
/* Retrieve the current values of all 3 bands for both channels */
if ((err= mixerGetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails,MIXER_GETCONTROLDETAILSF_VALUE)))
{
/* An error */
printf("Error #%d callingmixerGetControlDetails()/n", err);
}
else
{
unsigned long i,n;
/* Let's fetch the labels of all the items */
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
mixerControlDetails.dwControlID = 0x00000002;
mixerControlDetails.cChannels = 2;
mixerControlDetails.cMultipleItems = 3;
/* Give mixerGetControlDetails() the address of the
MIXERCONTROLDETAILS_LISTTEXTarray into which to return the labels */
mixerControlDetails.paDetails = &label[0];
/* Tell mixerGetControlDetails() how big each MIXERCONTROLDETAILS_LISTTEXTis */
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_LISTTEXT);
/* Retrieve the labels of all items for both channels, Note
that I specifyMIXER_GETCONTROLDETAILSF_LISTTEXT */
if ((err= mixerGetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails,MIXER_GETCONTROLDETAILSF_LISTTEXT)))
{
/* An error */
printf("Error #%dcalling mixerGetControlDetails()/n", err);
}
else
{
/* Print the values of allitems */
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()函数打开混音器然后将其返回值用作其它函数。不一定非得这样做。实际上,Mixer 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。
总结
MixerAPI是windows多媒体中最复杂的一组API。你需要一点时间来读懂这篇教程然后将它应用于你的程序中。但是Mixer API使得你可以操作任何已安装的声卡,而不必对某个特定的声卡编写特定的程序。
想获取更多关于混音器结构和API的信息,请参考MicrosoftDeveloper Network's reference upon audio mixers 。
微软提供了一个免费下载的关于使用Mixer API的样例程序。但是我发现此代码的注释太简单了,同时有很多和MixerAPI无关并且是不必要的代码。我已将此样例程序精简,使得其都是展示如何使用Mixer API的关键代码,并添加了很多注释。你可以下载我修改的版本Microsoft'sMixer Device Example,它将展示了如何显示所有的混音器设备和它们的线路/控件,以及调整控件的值。这个工程是基于Visual C++4.0的,因为它是一个普通的用C语言编写的windows应用程序,因此任何windows的C语言编译器应该都可以编译它。