现在,我们讲述混音器的目标——获取一个控件的值(这样你就可以将其当前的值显示给终端用户),和设置一个控件的值(这样可以让终端用户调整控件的值)。
想要获取或设置某个控件的值,你必须知道此控件的ID。可以通过mixerGetControlDetails()获取其当前值,通过mixerSetControlDetails()设置为某个特定的值。这些函数都使用一个类型为MIXERCONTROLDETAILS的结构体。你可以初始化其中的部分域来告诉
mixerGetControlDetails()/mixerSetControlDetails()你想设置/获取哪个控件的值。你同时还得提供另外一个即将存放值的结构。
例如,我们获取线路Speaker Out的音量滑动器控件的当前值,到目前位置,我们已知道了怎样获取这个控件的信息(例如,这个控件的ID)。为了获取控件的值,我们需要提供一个特殊的结构体来存放返回值。
我们需要使用什么样的结构?哦,这要看控件的类型了。音量滑动器控件的类型为
MIXERCONTROL_CONTROLTYPE_VOLUME,如果你回头看一下chart about the 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 is MIXERCONTROL_CONTROLTYPE_VOLUME */
MIXERCONTROLDETAILS_UNSIGNED value;
MIXERCONTROLDETAILS mixerControlDetails;
MMSYSTEM err;
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
/* Tell mixerGetControlDetails() which control whose value we want to retrieve. We do this by putting the desired control's ID number in dwControlID. Remember that the "Speaker Out" line'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 return the value */
mixerControlDetails.paDetails = &value;
/* Tell mixerGetControlDetails() how big the MIXERCONTROLDETAILS_UNSIGNED is */ 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 calling mixerGetControlDetails()\n", err); }
else
{ printf("It's value is %lu\n", value.dwValue); }
若要设置一个控件的值,你只需填充此结构,然后将其传递给mixerSetControlDetails()。你同时还要指定MIXER_SETCONTROLDETAILSF_VALUE结构。这里是一个样例程序,其将线路 Speaker Out的音量滑动器控件的值设置为31。
MIXERCONTROLDETAILS_UNSIGNED value;
MIXERCONTROLDETAILS mixerControlDetails;
MMSYSTEM 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 Out" line'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 place the value */
mixerControlDetails.paDetails = &value;
/* Tell mixerSetControlDetails() how big the MIXERCONTROLDETAILS_UNSIGNED is */ 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 calling mixerSetControlDetails()\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;
MMSYSTEM err;
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
/* Tell mixerGetControlDetails() which control whose value we want to retrieve. We do this by putting the desired control's ID number in dwControlID. Remember that the "Speaker Out" line'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 return the values */
mixerControlDetails.paDetails = &value[0]; /* Tell mixerGetControlDetails() how big each MIXERCONTROLDETAILS_UNSIGNED is */
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 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);
}
To set the values of both channels, you fill in the values of both MIXERCONTROLDETAILS_UNSIGNED structs. Here is an example of setting the left channel'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 not MIXERCONTROL_CONTROLF_UNIFORM */
MIXERCONTROLDETAILS_UNSIGNED value[2];
MIXERCONTROLDETAILS mixerControlDetails;
MMSYSTEM 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 Out" line'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 place the values */
mixerControlDetails.paDetails = &value[0];
/* Tell mixerSetControlDetails() how big each MIXERCONTROLDETAILS_UNSIGNED is */ 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 calling mixerSetControlDetails()\n", err);
}
当然,一个控件或许有2个以上的控件。对一个给定的控件,你必须提供足够大的结构来容纳所有的声道的值。因此,你必须根据需要来开辟数组空间。
一次存取一个控件中的某几个声道是非法的。例如,一个控件有8个声道,但是你只取其前2个声道的值,这是不允许的。你必须同时存取一个控件的所有声道才可以。但是这里有一条有关设置一个值的规则:如果你仅仅设置第一个声道的值,那么
mixerSetControlDetails()自动将控件设置MIXERCONTROL_CONTROLF_UNIFORM标志。最终结果是此控件的所有声道都被设置为这个值。因此,你可以通过仅仅设置第一个声道的值来达到将所有的声道设置为同一个值的目的。
你不会经常碰到“多条目”控件。一个多条目控件就是那种一个声道关联着多个值的控件。
下面是一个图形均衡器控件。让我们简单的学习一下这个样例。假设一个声卡中有以下3个图形均衡器。
这个控件有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 this control */ MIXERCONTROL_CONTROLTYPE_EQUALIZER, /* type of control */ MIXERCONTROL_CONTROLF_UNIFORM|MIXERCONTROL_CONTROLF_MULTIPLE, /* flag bits */ 3, /* # of items perchannel*/
"EQ", /* Short name for this control */
"Graphic Equalizer", /* 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 */
};
首先,注意控件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;
MMSYSTEM 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 return the values */ mixerControlDetails.paDetails = &value[0];
/* Tell mixerGetControlDetails() how big each MIXERCONTROLDETAILS_UNSIGNED is */
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 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);
}
To set the values of all 3 bands, you fill in the values of the MIXERCONTROLDETAILS_UNSIGNED structs. Here is an example of setting the Low band 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;
MMSYSTEM 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 place the values */ mixerControlDetails.paDetails = &value[0]; /* Tell mixerSetControlDetails() how big each MIXERCONTROLDETAILS_UNSIGNED is */
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 calling mixerSetControlDetails()\n", err);
}
现在我们将控件的MIXERCONTROL_CONTROLF_UNIFORM标志去掉。因此,每个声道的每个条目都有其自己的值。因为Speaker Out 有两个声道,那意味着我们需要总计为2 (声道) * 3 (条目)个结构,也就是6个值。我们的图形均衡器如下图所示:
Left Channel | Right Channel |
我们需要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;
MMSYSTEM 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 return the values */ mixerControlDetails.paDetails = &value[0]; /* Tell mixerGetControlDetails() how big each MIXERCONTROLDETAILS_UNSIGNED is */
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 calling mixerGetControlDetails()\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_LISTTEXT array into which to return the labels */
mixerControlDetails.paDetails = &label[0]; /* Tell mixerGetControlDetails() how big each MIXERCONTROLDETAILS_LISTTEXT is */
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_LISTTEXT); /* Retrieve the labels of all items for both channels, Note that I specify MIXER_GETCONTROLDETAILSF_LISTTEXT */
if ((err = mixerGetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails, MIXER_GETCONTROLDETAILSF_LISTTEXT)))
{ /* An error */
printf("Error #%d calling mixerGetControlDetails()\n", err);
}
else
{ /* Print the values of all items */
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。
Mixer API是windows多媒体中最复杂的一组API。你需要一点时间来读懂这篇教程然后将它应用于你的程序中。但是Mixer API使得你可以操作任何已安装的声卡,而不必对某个特定的声卡编写特定的程序。
想获取更多关于混音器结构和API的信息,请参考Microsoft Developer Network's reference upon audio mixers 。
微软提供了一个免费下载的关于使用Mixer API的样例程序。但是我发现此代码的注释太简单了,同时有很多和Mixer API无关并且是不必要的代码。我已将此样例程序精简,使得其都是展示如何使用Mixer API的关键代码,并添加了很多注释。你可以下载我修改的版本Microsoft's Mixer Device Example,它将展示了如何显示所有的混音器设备和它们的线路/控件,以及调整控件的值。这个工程是基于Visual C++4.0的,因为它是一个普通的用C语言编写的windows应用程序,因此任何windows的C语言编译器应该都可以编译它。