天天看點

混音器程式設計接口讨論

為了了解混音器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輸出),然後取得這個聲霸卡的混音器裝置句柄:

WAVEFORMATEX  waveFormat;

HWAVEOUT      hWaveOut;

err = waveOutOpen(&hWaveOut, WAVE_MAPPER, &waveFormat, (DWORD)WaveOutProc, 0, CALLBACK_FUNCTION);

    printf("ERROR: Can't open WAVE Out Device! -- %08X\n", err);

    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);

    printf("ERROR: Can't get Mixer Device ID! -- %08X\n", err);

    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 

這是隻有兩個狀态(值)可供選擇的控制器,是以通過一個按鈕來調節。

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.");

    case MIXERCONTROL_CT_CLASS_METER:

        printf("It's a meter class.");

    case MIXERCONTROL_CT_CLASS_NUMBER:

        printf("It's a number class.");

    case MIXERCONTROL_CT_CLASS_SLIDER:

        printf("It's a slider class.");

    case MIXERCONTROL_CT_CLASS_TIME:

        printf("It's a time class.");

    case MIXERCONTROL_CT_CLASS_CUSTOM:

        printf("It's a custom class.");

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 

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,      

    0x0100, 

    "Example Sound Card",

    2,      

};

下面是個将MIXERCAPS傳遞給mixerGetDevCaps()以使windows幫助我們填充以上所有字段的例子(假設我們已經打開了混音器并且已經将其句柄存放到變量mixerHandle中):

MMRESULT      err;

if (!(err = mixerGetDevCaps((UINT)mixerHandle, &mixcaps, sizeof(MIXERCAPS))))

    printf("Error #%d calling mixerGetDevCaps()\n", err);

一條線路的資訊被存放在MIXERLINE結構體(也定義在MMSYSTEM.H)内。我們假設我們的揚聲器輸出目标線路有兩個控制器:一個滑動音量調節器(用來控制輸出到揚聲器的混音音量)和一個靜音開關(用來對所有混音靜音)。下面是用于揚聲器輸出目标線路的MIXERLINE結構體的例子: 

MIXERLINE mixerline_SpkrOut = {

    sizeof(MIXERLINE),             

    0,                             

    0xFFFF0000,                    

    MIXERLINE_LINEF_ACTIVE,        

    MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, 

    2,                             

    4,                             

    "Spkr Out",                    

    "Speaker Out",                 

    MIXERLINE_TARGETTYPE_WAVEOUT,  

這裡有一些需要注意的地方。首先,揚聲器輸出目标線路的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 = {

    1,                             

    0xFFFF0001,                    

    MIXERLINE_COMPONENTTYPE_DST_WAVEIN, 

    "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;

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)))

當上面的調用傳回後, mixerGetLineInfo() 會根據ADC WAVE輸入(mixerline_WaveIn)的MIXERLINE結構體填充我們的MIXERLINE結構體(畢竟,ADC WAVE輸入是我們樣例混音器的第二個線路,它索引值為1)。

現在,你應該明白了如何根據索引值去枚舉目标線路。下面是個示例,它列印出混音器中所有目标線路的名字:

unsigned long i;

    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),

    0x00000000,                    

    MIXERLINE_LINEF_ACTIVE|MIXERLINE_LINEF_SOURCE,

    0,

    MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC, 

    "CD Audio",                    

    "Internal CD Audio",           

    MIXERLINE_TARGETTYPE_UNDEFINED,

MIXERLINE mixerline_Synth = {

    0x00000001,                    

    MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER, 

    "Synth",                       

MIXERLINE mixerline_WaveOut = {

    0x00000002,                    

    MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT, 

    "Wave Out",                    

    "DAC Wave Out",                

MIXERLINE mixerline_Mic = {

    3,                             

    0x00000003,                    

    MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, 

    "Mic",                         

    "Microphone Input",            

你必須注意的一件事是:不同于目标線路,源線路的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.dwSource = 0;

if ((err = mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerline, MIXER_GETLINEINFOF_SOURCE)))

當上面的調用傳回後, mixerGetLineInfo() 會根據内部CD音頻(mixerline_CD)的MIXERLINE結構體填充我們的MIXERLINE結構體(畢竟,揚聲器輸出是我們樣例混音器的第一條線路,它索引值為0)。現在根據示例,你可以從MIXERLINE結構體的dwLineID字段取得線路的ID。

現在,如果你想取得揚聲器輸出目标線路的第二條源線路的資訊,唯一不同的就是你設定給dwSource字段的值是第二個源線路的索引(1),如下:

mixerline.dwSource = 1;

     printf("Error #%d calling mixerGetLineInfo()\n", err);

當上面的調用傳回後, mixerGetLineInfo() 會根據合成器(mixerline_Synth)的MIXERLINE結構體填充我們的MIXERLINE結構體。

現在,你應該明白了如何根據索引值去枚舉(每條目标線路的)源線路。下面是個示例,它列印出混音器中所有目标線路以及它們各自的所有源線路的名字:

unsigned long i, n, numSrc;

      for (i = 0; i < mixercaps.cDestinations; i++)

            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.dwLineID = 0x00000003;

if ((err = mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerline, MIXER_GETLINEINFOF_LINEID)))

以上方法均适用于源線路和目标線路。隻要你知道了一條線路的ID,你就可以根據它直接擷取線路的資訊,而不需要知道其索引。

通過類型取得線路資訊

通常,你不需要知道混音器中所有的線路。你的程式可能僅僅隻和某個類型的線路打交道。比如,假設你正在編寫一個MIDI檔案播放器。現在,我們的樣例聲霸卡的某些元件對于你來說根本毫無用處。MIDI不是數字音頻資料,是以DAC WAVE輸入元件(以及所有連接配接到它的所有源線路)對你來說毫無意義。同樣,目标線路揚聲器輸出的源線路内部CD音頻、DAC WAVE輸出和麥克風輸入對你來說也毫無意義。我們的樣例聲霸卡上唯一一個可以處理MIDI資料回放的元件是連接配接揚聲器輸出的合成器元件。正是這個線路的控制器影響着MIDI資料的回放。

是以,與其枚舉混音器中的所有線路直到碰到一條類型為MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER的線路,不如用mixerGetLineInfo()直接擷取指定類型的線路資訊。你隻需要将MIXERLINE的dwComponentType字段指定為你想要的線路類型,然後在調用mixerGetLineInfo()的時候指定MIXER_GETLINEINFOF_COMPONENTTYPE即可,如下所示:

mixerline.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER;

if ((err = mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerline, MIXER_GETLINEINFOF_COMPONENTTYPE)))

系統将會用混音器中第一條類型為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",           

    65535,                          

    0, 0, 0, 0,                     

    31,                             

    0, 0, 0, 0, 0,                  

MIXERCONTROL mixerctl_Spkr_Mute = {

    sizeof(MIXERCONTROL),

    0x00000001,                     

    MIXERCONTROL_CONTROLTYPE_MUTE,  

    MIXERCONTROL_CONTROLF_UNIFORM,

    "Mute",                         

    "Speaker Out Mute",             

    1,                              

    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.cControls = 1;

mixerLineControls.dwControlID = 0x00000000;

mixerLineControls.pamxctrl = &mixerControlArray;

if ((err = mixerGetLineControls((HMIXEROBJ)mixerHandle, &mixerLineControls, MIXER_GETLINECONTROLSF_ONEBYID)))

通過類型取得控制器資訊

通常情況下,你不必知道某個線路中所有控制器的資訊。在你的程式中你可能僅僅隻和某個類型的控制器打交道。比如,假設你在寫一個簡單的MIDI檔案回放程式,你提供給終端使用者的隻是音樂合成器的一個滑動音量調節器。前面我們已經知道怎樣通過類型擷取MIDI回放線路并擷取此線路的資訊,比如線路ID。你可以用這個線路ID來搜尋此線路中某個特定類型的控制器,比如你可以尋找一個類型為MIXERCONTROL_CONTROLTYPE_VOLUME的控制器。

是以,與其通過枚舉線路中所有控制器直到你碰到一個類型為MIXERCONTROL_CONTROLTYPE_VOLUME的控制器,不如用mixerGetLineControls()直接通過類型擷取控制器。你隻需要将MIXERCONTROLS的字段dwControlType設定為你想要的類型,然後在調用mixerGetLineControls()的時候指定MIXER_GETLINECONTROLSF_ONEBYTYPE标志(假設你已經擷取了合成器線路的ID,并存儲在變量SynthID中):

mixerLineControls.dwLineID = SynthID;

mixerLineControls.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;

if ((err = mixerGetLineControls((HMIXEROBJ)mixerHandle, &mixerLineControls, MIXER_GETLINECONTROLSF_ONEBYTYPE)))

系統将會使用此線路中第一個擁有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);

    printf("It's value is %lu\n", value.dwValue);

若要設定一個控制器的值,你隻需填充此結構,然後将其傳遞給mixerSetControlDetails()。你同時還要指定MIXER_SETCONTROLDETAILSF_VALUE結構。這裡是一個樣例程式,其将揚聲器輸出線路的滑動音量調節器的值設定為31。

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.cChannels = 2;

mixerControlDetails.paDetails = &value[0];

    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.

value[0].dwValue = 31;

value[1].dwValue = 0;

當然,一個控制器或許有2個以上的聲道。對一個給定的控制器,你必須提供足夠大的結構來容納所有的聲道的值。是以,通常你必須根據需要來開辟數組空間。

一次存取一個控制器中的某幾個聲道是非法的。例如,一個控制器有8個聲道,但是你隻取其前2個聲道的值,這是不允許的。你必須同時存取一個控制器的所有聲道才行。但是這裡有一條有關設定一個值的規則:如果你僅僅設定第一個聲道的值,那麼mixerSetControlDetails()自動将控制器設定MIXERCONTROL_CONTROLF_UNIFORM标志。最終結果是此控制器的所有聲道都被設定為這個值。是以,你可以通過僅僅設定第一個聲道的值來達到将所有的聲道設定為同一個值的目的。

多元素控制器

你不會常常碰到多元素的控制器。一個多元素控制器就是每個聲道關聯着多個值的控制器。圖形化均衡器是一個例子。讓我們來看一個簡單的,假設一個聲霸卡内置有以下帶着3個通道(band)的圖形化均衡器:

這個控制器有3個值與其關聯—與“低通道”關聯的值、與“中通道”關聯的值、與“高通道”關聯的值(假設每個通道都能設定為不同的值,否則它就是一個無用的圖形化均衡器了)。這樣表現出來的就是一個多元素控制器,它有3個元素(值)與其關聯。

我們再進一步假設這個控制器屬于揚聲器輸出線路。我們來看一下MIXERCONTROL結構體。一個多元素控制器的MIXERCONTROL的字段dwControlType的值會設定有MIXERCONTROL_CONTROLF_MULTIPLE位标志,同時MIXERCONTROL的cMultipleItems字段會告訴你每個聲道有幾個元素。 

MIXERCONTROL mixerctl_EQ = {

    0x00000002,                     

    MIXERCONTROL_CONTROLTYPE_EQUALIZER,

    MIXERCONTROL_CONTROLF_UNIFORM|MIXERCONTROL_CONTROLF_MULTIPLE,  

    3,                              

    "EQ",                           

    "Graphic Equalizer",            

首先,注意控制器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.dwControlID = 0x00000002;

mixerControlDetails.cMultipleItems = 3;

    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。

value[2].dwValue = 62;

現在我們将控制器的MIXERCONTROL_CONTROLF_UNIFORM标志去掉。是以,每個聲道的每個元素都有其自己的值。因為揚聲器輸出有兩個聲道,那意味着我們的控制器需要總計為2 (聲道) * 3 (元素)個結構,也就是6個值。我們的圖形化均衡器如下圖所示: 

Left Channel

Right Channel

左聲道右聲道

我們需要6個類型為MIXERCONTROLDETAILS_UNSIGNED的結構來存放所有聲道的所有元素的值。對了,在前面的樣例中,我都将這些元素的标簽設定過了。如果你希望将它們輸出,你就應該向混音器查詢這些值。為了達到目的,你必須提供一個類型為MIXERCONTROLDETAILS_LISTTEXT結構的數組,就如同你提供一組類型為MIXERCONTROLDETAILS_UNSIGNED的結構來擷取所有聲道的所有元素的值一樣。 

MIXERCONTROLDETAILS_UNSIGNED value[6];

MIXERCONTROLDETAILS_LISTTEXT label[6];

    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語言編譯器應該都可以編譯它。

<a href="http://www.wiz.cn/">Published by Wiz</a>

本文章由windviki原創。轉載請注明出處。

繼續閱讀