最早出現(xiàn)在Linux上的音頻編程接口是OSS(Open Sound System),它由一套完整的內(nèi)核驅(qū)動(dòng)程序模塊組成,可以為絕大多數(shù)聲卡提供統(tǒng)一的編程接口。OSS出現(xiàn)的歷史相對(duì)較長(zhǎng),這些內(nèi)核模塊中的一部分(OSS/Free)是與Linux內(nèi)核源碼共同免費(fèi)發(fā)布的,另外一些則以二進(jìn)制的形式由4Front Technologies公司提供。由于得到了商業(yè)公司的鼎力支持,OSS已經(jīng)成為在Linux下進(jìn)行音頻編程的事實(shí)標(biāo)準(zhǔn),支持OSS的應(yīng)用程序能夠在絕大多數(shù)聲卡上工作良好。
雖然OSS已經(jīng)非常成熟,但它畢竟是一個(gè)沒(méi)有完全開(kāi)放源代碼的商業(yè)產(chǎn)品,ALSA(Advanced Linux Sound Architecture)恰好彌補(bǔ)了這一空白,它是在Linux下進(jìn)行音頻編程時(shí)另一個(gè)可供選擇的聲卡驅(qū)動(dòng)程序。ALSA除了像OSS那樣提供了一組內(nèi)核驅(qū)動(dòng)程序模塊之外,還專門(mén)為簡(jiǎn)化應(yīng)用程序的編寫(xiě)提供了相應(yīng)的函數(shù)庫(kù),與OSS提供的基于ioctl的原始編程接口相比,ALSA函數(shù)庫(kù)使用起來(lái)要更加方便一些。ALSA的主要特點(diǎn)有:
1)支持多種聲卡設(shè)備?
2)模塊化的內(nèi)核驅(qū)動(dòng)程序?
3)支持SMP和多線程?
4)提供應(yīng)用開(kāi)發(fā)函數(shù)庫(kù)?
5)兼容OSS應(yīng)用程序
ALSA和OSS最大的不同之處在于ALSA是由志愿者維護(hù)的自由項(xiàng)目,而OSS則是由公司提供的商業(yè)產(chǎn)品,因此在對(duì)硬件的適應(yīng)程度上OSS要優(yōu)于ALSA,它能夠支持的聲卡種類更多。ALSA雖然不及OSS運(yùn)用得廣泛,但卻具有更加友好的編程接口,并且完全兼容于OSS,對(duì)應(yīng)用程序員來(lái)講無(wú)疑是一個(gè)更佳的選擇。
兩種音頻編程接口驅(qū)動(dòng)的組成如下:
1) Linux OSS 音頻設(shè)備驅(qū)動(dòng)的組成、mixer 接口、dsp 接口及用戶空間編程方法。
2) Linux ALSA 音頻設(shè)備驅(qū)動(dòng)的組成、card 和組件管理、PCM 設(shè)備、control 接口、AC97 API及用戶空間編程方法。
1. 數(shù)字音頻設(shè)備
目前,手機(jī)、PDA、MP3 等許多嵌入式設(shè)備中包含了數(shù)字音頻設(shè)備,一個(gè)典型的數(shù)字音頻系統(tǒng)的電路組成為:嵌入式微控制器/DSP 中集成了PCM、IIS 或AC97 音頻接口,通過(guò)這些接口連接外部的音頻編解碼器即可實(shí)現(xiàn)聲音的AD 和DA 轉(zhuǎn)換,功放完成模擬信號(hào)的放大功能。
音頻編解碼器是數(shù)字音頻系統(tǒng)的核心,衡量它的指標(biāo)主要有:
? 采樣頻率
?????? 采樣的過(guò)程就是將通常的模擬音頻信號(hào)的電信號(hào)轉(zhuǎn)換成二進(jìn)制碼0 和1 的過(guò)程,這些0 和1 便構(gòu)成了數(shù)字音頻文件。如圖17.2 中的正弦曲線代表原始音頻曲線,方格代表采樣后得到的結(jié)果,二者越吻合說(shuō)明采樣結(jié)果越好。采樣頻率是每秒鐘的采樣次數(shù),我們常說(shuō)的 44.1kHz 采樣頻率就是每秒鐘采樣44100 次。理論上采樣頻率越高,轉(zhuǎn)換精度越高,目前主流的采樣頻率是48kHz。
? 量化精度
?????? 量化精度是指對(duì)采樣數(shù)據(jù)分析的精度,比如24bit 量化精度就是是將標(biāo)準(zhǔn)電平信號(hào)按照2 的24 次方進(jìn)行分析,也就是說(shuō)將圖17.2 中的縱坐標(biāo)等分為224 等分。量化精度越高,聲音就越逼真。
2. 音頻設(shè)備硬件接口
2.1 PCM 接口
?????? 針對(duì)不同的數(shù)字音頻子系統(tǒng),出現(xiàn)了幾種微處理器或DSP 與音頻器件間用于數(shù)字轉(zhuǎn)換的接口。最簡(jiǎn)單的音頻接口是PCM(脈沖編碼調(diào)制)接口,該接口由時(shí)鐘脈沖(BCLK)、幀同步信號(hào)(FS)及接收數(shù)據(jù)(DR)和發(fā)送數(shù)據(jù)(DX)組成。在FS 信號(hào)的上升沿,數(shù)據(jù)傳輸從MSB(Most Significant Bit)字開(kāi)始,F(xiàn)S 頻率等于采樣率。FS 信號(hào)之后開(kāi)始數(shù)據(jù)字的傳輸,單個(gè)的數(shù)據(jù)位按順序進(jìn)行傳輸,1 個(gè)時(shí)鐘周期傳輸1 個(gè)數(shù)據(jù)字。發(fā)送MSB 時(shí),信號(hào)的等級(jí)首先降到最低,以避免在不同終端的接口使用不同的數(shù)據(jù)方案時(shí)造成MSB 的丟失。PCM 接口很容易實(shí)現(xiàn),原則上能夠支持任何數(shù)據(jù)方案和任何采樣率,但需要每個(gè)音頻通道獲得一個(gè)獨(dú)立的數(shù)據(jù)隊(duì)列。
2.2 IIS 接口
???? IIS 接口(Inter-IC Sound)在20 世紀(jì)80 年代首先被飛利浦用于消費(fèi)音頻,并在一個(gè)稱為L(zhǎng)RCLK(Left/RightCLOCK)的信號(hào)機(jī)制中經(jīng)過(guò)多路轉(zhuǎn)換,將兩路音頻信號(hào)變成單一的數(shù)據(jù)隊(duì)列。當(dāng)LRCLK 為高時(shí),左聲道數(shù)據(jù)被傳輸;LRCLK 為低時(shí),右聲道數(shù)據(jù)被傳輸。與PCM 相比,IIS 更適合于立體聲系統(tǒng)。對(duì)于多通道系統(tǒng),在同樣的BCLK 和LRCLK 條件下,并行執(zhí)行幾個(gè)數(shù)據(jù)隊(duì)列也是可能的。
2.3 AC97 接口
?????? AC'97(Audio Codec 1997)是以Intel 為首的五個(gè)PC 廠商Intel、Creative Labs、NS、Analog????? Device與Yamaha 共同提出的規(guī)格標(biāo)準(zhǔn)。與PCM 和IIS 不同,AC'97 不只是一種數(shù)據(jù)格式,用于音頻編碼的內(nèi)部架構(gòu)規(guī)格,它還具有控制功能。AC'97 采用AC-Link 與外部的編解碼器相連,AC-Link 接口包括位時(shí)鐘(BITCLK)、同步信號(hào)校正(SYNC)和從編碼到處理器及從處理器中解碼(SDATDIN 與SDATAOUT)的數(shù)據(jù)隊(duì)列。AC'97數(shù)據(jù)幀以SYNC 脈沖開(kāi)始,包括12 個(gè)20 位時(shí)間段(時(shí)間段為標(biāo)準(zhǔn)中定義的不同的目的服務(wù))及16 位“tag”段,共計(jì)256 個(gè)數(shù)據(jù)序列。例如,時(shí)間段“1”和“2”用于訪問(wèn)編碼的控制寄存器,而時(shí)間段“3”和“4”分別負(fù)載左、右兩個(gè)音頻通道。“tag”段表示其他段中哪一個(gè)包含有效數(shù)據(jù)。把幀分成時(shí)間段使傳輸控制信號(hào)和音頻數(shù)據(jù)僅通過(guò)4 根線到達(dá)9 個(gè)音頻通道或轉(zhuǎn)換成其他數(shù)據(jù)流成為可能。與具有分離控制接口的IIS方案相比,AC'97 明顯減少了整體管腳數(shù)。
PCM、IIS 和AC97 各有其優(yōu)點(diǎn)和應(yīng)用范圍,例如在CD、MD、MP3 隨身聽(tīng)多采用IIS 接口,移動(dòng)電話會(huì)采用PCM 接口,具有音頻功能的PDA 則多使用和PC 一樣的AC'97 編碼格式。
3. Linux OSS 音頻設(shè)備驅(qū)動(dòng)及應(yīng)用編程
3.1 OSS 驅(qū)動(dòng)的組成
???? OSS 標(biāo)準(zhǔn)中有2 個(gè)最基本的音頻設(shè)備:mixer(混音器)和DSP(數(shù)字信號(hào)處理器)。
在聲卡的硬件電路中,mixer 是一個(gè)很重要的組成部分,它的作用是將多個(gè)信號(hào)組合或者疊加在一起,對(duì)于不同的聲卡來(lái)說(shuō),其混音器的作用可能各不相同。OSS 驅(qū)動(dòng)中,/dev/mixer 設(shè)備文件是應(yīng)用程序?qū)ixer進(jìn)行操作的軟件接口。
混音器電路通常由兩個(gè)部分組成:輸入混音器(input mixer)和輸出混音器(output mixer)。
輸入混音器負(fù)責(zé)從多個(gè)不同的信號(hào)源接收模擬信號(hào),這些信號(hào)源有時(shí)也被稱為混音通道或者混音設(shè)備。模擬信號(hào)通過(guò)增益控制器和由軟件控制的音量調(diào)節(jié)器后,在不同的混音通道中進(jìn)行級(jí)別(level)調(diào)制,然后被送到輸入混音器中進(jìn)行聲音的合成。混音器上的電子開(kāi)關(guān)可以控制哪些通道中有信號(hào)與混音器相連,有些聲卡只允許連接一個(gè)混音通道作為錄音的音源,而有些聲卡則允許對(duì)混音通道做任意的連接。經(jīng)過(guò)輸入混音器處理后的信號(hào)仍然為模擬信號(hào),它們將被送到A/D 轉(zhuǎn)換器進(jìn)行數(shù)字化處理。
輸出混音器的工作原理與輸入混音器類似,同樣也有多個(gè)信號(hào)源與混音器相連,并且事先都經(jīng)過(guò)了增益調(diào)節(jié)。當(dāng)輸出混音器對(duì)所有的模擬信號(hào)進(jìn)行了混合之后,通常還會(huì)有一個(gè)總控增益調(diào)節(jié)器來(lái)控制輸出聲音的大小,此外還有一些音調(diào)控制器來(lái)調(diào)節(jié)輸出聲音的音調(diào)。經(jīng)過(guò)輸出混音器處理后的信號(hào)也是模擬信號(hào),它們最終會(huì)被送給喇叭或者其它的模擬輸出設(shè)備。對(duì)混音器的編程包括如何設(shè)置增益控制器的級(jí)別,以及怎樣在不同的音源間進(jìn)行切換,這些操作通常來(lái)講是不連續(xù)的,而且不會(huì)像錄音或者放音那樣需要占用大量的計(jì)算機(jī)資源。
由于混音器的操作不符合典型的讀/寫(xiě)操作模式,因此除了open()和close()兩個(gè)系統(tǒng)調(diào)用之外,大部分的操作都是通過(guò)ioctl()系統(tǒng)調(diào)用來(lái)完成的。與/dev/dsp 不同,/dev/mixer 允許多個(gè)應(yīng)用程序同時(shí)訪問(wèn),并且混音器的設(shè)置值會(huì)一直保持到對(duì)應(yīng)的設(shè)備文件被關(guān)閉為止。DSP 也稱為編解碼器,實(shí)現(xiàn)錄音(錄音)和放音(播放),其對(duì)應(yīng)的設(shè)備文件是/dev/dsp 或/dev/sound/dsp。
OSS 聲卡驅(qū)動(dòng)程序提供的/dev/dsp 是用于數(shù)字采樣和數(shù)字錄音的設(shè)備文件,向該設(shè)備寫(xiě)數(shù)據(jù)即意味著激活聲卡上的D/A 轉(zhuǎn)換器進(jìn)行放音,而向該設(shè)備讀數(shù)據(jù)則意味著激活聲卡上的A/D 轉(zhuǎn)換器進(jìn)行錄音。在從DSP 設(shè)備讀取數(shù)據(jù)時(shí),從聲卡輸入的模擬信號(hào)經(jīng)過(guò)A/D 轉(zhuǎn)換器變成數(shù)字采樣后的樣本,保存在聲卡驅(qū)動(dòng)程序的內(nèi)核緩沖區(qū)中,當(dāng)應(yīng)用程序通過(guò) read()系統(tǒng)調(diào)用從聲卡讀取數(shù)據(jù)時(shí),保存在內(nèi)核緩沖區(qū)中的數(shù)字采樣結(jié)果將被復(fù)制到應(yīng)用程序所指定的用戶緩沖區(qū)中。需要指出的是,聲卡采樣頻率是由內(nèi)核中的驅(qū)動(dòng)程序所決定的,而不取決于應(yīng)用程序從聲卡讀取數(shù)據(jù)的速度。如果應(yīng)用程序讀取數(shù)據(jù)的速度過(guò)慢,以致低于聲卡的采樣頻率,那么多余的數(shù)據(jù)將會(huì)被丟棄(即overflow);如果讀取數(shù)據(jù)的速度過(guò)快,以致高于聲卡的采樣頻率,那么聲卡驅(qū)動(dòng)程序?qū)?huì)阻塞那些請(qǐng)求數(shù)據(jù)的應(yīng)用程序,直到新的數(shù)據(jù)到來(lái)為止。在向DSP 設(shè)備寫(xiě)入數(shù)據(jù)時(shí),數(shù)字信號(hào)會(huì)經(jīng)過(guò)D/A 轉(zhuǎn)換器變成模擬信號(hào),然后產(chǎn)生出聲音。應(yīng)用程序?qū)懭霐?shù)據(jù)的速度應(yīng)該至少等于聲卡的采樣頻率,過(guò)慢會(huì)產(chǎn)生聲音暫?;蛘咄nD的現(xiàn)象(即underflow)。如果用戶寫(xiě)入過(guò)快的話,它會(huì)被內(nèi)核中的聲卡驅(qū)動(dòng)程序阻塞,直到硬件有能力處理新的數(shù)據(jù)為止。
與其它設(shè)備有所不同,聲卡通常不需要支持非阻塞(non-blocking)的I/O 操作。即便內(nèi)核OSS 驅(qū)動(dòng)提供了非阻塞的I/O 支持,用戶空間也不宜采用。無(wú)論是從聲卡讀取數(shù)據(jù),或是向聲卡寫(xiě)入數(shù)據(jù),事實(shí)上都具有特定的格式(format),如無(wú)符號(hào)8 位、單聲道、8KHz 采樣率,如果默認(rèn)值無(wú)法達(dá)到要求,可以通過(guò)ioctl()系統(tǒng)調(diào)用來(lái)改變它們。通常說(shuō)來(lái),在應(yīng)用程序中打開(kāi)設(shè)備文件/dev/dsp 之后,接下去就應(yīng)該為其設(shè)置恰當(dāng)?shù)母袷剑缓蟛拍軓穆暱ㄗx取或者寫(xiě)入數(shù)據(jù)。
3.2 mixer 接口
??????? int register_sound_mixer(struct file_operations *fops, int dev);
?????? 上述函數(shù)用于注冊(cè)1 個(gè)混音器,第1 個(gè)參數(shù)fops 即是文件操作接口,第2 個(gè)參數(shù)dev 是設(shè)備編號(hào),如果填入-1,則系統(tǒng)自動(dòng)分配1 個(gè)設(shè)備編號(hào)。mixer 是1 個(gè)典型的字符設(shè)備,因此編碼的主要工作是實(shí)現(xiàn)file_operations 中的open()、ioctl()等函數(shù)。mixer 接口file_operations 中的最重要函數(shù)是ioctl(),它實(shí)現(xiàn)混音器的不同IO 控制命令,下面的代碼清單給出了1 個(gè)ioctl()的范例。
mixer()接口ioctl()函數(shù)范例:
[cpp]?view plain?copy
static?int?mixdev_ioctl(struct?inode?*inode,?struct?file?*file,?unsigned?int?cmd,?unsigned?long?arg)??
{??
...??
switch?(cmd)??
{??
case?SOUND_MIXER_READ_MIC:??
...??
case?SOUND_MIXER_WRITE_MIC:??
...??
case?SOUND_MIXER_WRITE_RECSRC:??
...??
case?SOUND_MIXER_WRITE_MUTE:??
...??
}??
...??
return?mixer_ioctl(codec,?cmd,?arg);??
}??
3.3 DSP 接口
??????? int register_sound_dsp(struct file_operations *fops, int dev);
?????? 上述函數(shù)與register_sound_mixer()類似,它用于注冊(cè)1 個(gè)dsp 設(shè)備,第1 個(gè)參數(shù)fops 即是文件操作接口,第2 個(gè)參數(shù)dev 是設(shè)備編號(hào),如果填入-1,則系統(tǒng)自動(dòng)分配1 個(gè)設(shè)備編號(hào)。dsp 也是1 個(gè)典型的字符設(shè)備,因此編碼的主要工作是實(shí)現(xiàn)file_operations 中的read()、write()、ioctl()等函數(shù)。dsp 接口file_operations 中的read()和write()函數(shù)非常重要,read()函數(shù)從音頻控制器中獲取錄音數(shù)據(jù)到緩沖區(qū)并拷貝到用戶空間,write()函數(shù)從用戶空間拷貝音頻數(shù)據(jù)到內(nèi)核空間緩沖區(qū)并最終發(fā)送到音頻控制器。
dsp 接口file_operations 中的ioctl()函數(shù)處理對(duì)采樣率、量化精度、DMA 緩沖區(qū)塊大小等參數(shù)設(shè)置IO控制命令的處理。在數(shù)據(jù)從緩沖區(qū)拷貝到音頻控制器的過(guò)程中,通常會(huì)使用DMA,DMA對(duì)聲卡而言非常重要。例如,在放音時(shí),驅(qū)動(dòng)設(shè)置完DMA 控制器的源數(shù)據(jù)地址(內(nèi)存中DMA 緩沖區(qū))、目的地址(音頻控制器FIFO)和DMA 的數(shù)據(jù)長(zhǎng)度,DMA 控制器會(huì)自動(dòng)發(fā)送緩沖區(qū)的數(shù)據(jù)填充FIFO,直到發(fā)送完相應(yīng)的數(shù)據(jù)長(zhǎng)度后才中斷一次。
在OSS 驅(qū)動(dòng)中,建立存放音頻數(shù)據(jù)的環(huán)形緩沖區(qū)(ring buffer)通常是值得推薦的方法。此外,在OSS 驅(qū)動(dòng)中,一般會(huì)將1 個(gè)較大的DMA 緩沖區(qū)分成若干個(gè)大小相同的塊(這些塊也被稱為“段”,即fragment),驅(qū)動(dòng)程序使用DMA 每次在聲音緩沖區(qū)和聲卡之間搬移一個(gè)fragment。在用戶空間,可以使用ioctl()系統(tǒng)調(diào)用來(lái)調(diào)整塊的大小和個(gè)數(shù)。除了read()、write()和ioctl()外,dsp 接口的poll()函數(shù)通常也需要被實(shí)現(xiàn),以向用戶反饋目前能否讀寫(xiě)DMA 緩沖區(qū)。在OSS 驅(qū)動(dòng)初始化過(guò)程中,會(huì)調(diào)用register_sound_dsp()和register_sound_mixer()注冊(cè)dsp 和mixer 設(shè)備;在模塊卸載的時(shí)候,調(diào)用的代碼如下:
??? OSS 驅(qū)動(dòng)初始化注冊(cè)dsp 和mixer設(shè)備:
[cpp]?view plain?copy
static?int?myoss_init(void)??
{??
struct?oss_state?*s?=?&myoss_state;??
...??
//注冊(cè)dsp?設(shè)備??
if?((audio_dev_dsp?=?register_sound_dsp(&xxx_audio_fops,?-?1))?0)??
goto?err_dev1;??
//注冊(cè)mixer?設(shè)備??
if?((audio_dev_mixer?=?register_sound_mixer(&xxx_mixer_fops,?-?1))?0)??
goto?err_dev2;??
...??
}??
void?__exit?xxx_exit(void)??
{??
//注銷dsp?和mixer?設(shè)備接口??
unregister_sound_dsp(audio_dev_dsp);??
unregister_sound_mixer(audio_dev_mixer);??
...??
}??
3.4 OSS 用戶空間編程
1、DSP 編程
對(duì)OSS 驅(qū)動(dòng)聲卡的編程使用Linux 文件接口函數(shù),DSP 接口的操作一般包括如下幾個(gè)步驟:
① 打開(kāi)設(shè)備文件/dev/dsp
?????? 采用何種模式對(duì)聲卡進(jìn)行操作也必須在打開(kāi)設(shè)備時(shí)指定,對(duì)于不支持全雙工的聲卡來(lái)說(shuō),應(yīng)該使用只讀或者只寫(xiě)的方式打開(kāi),只有那些支持全雙工的聲卡,才能以讀寫(xiě)的方式打開(kāi),這還依賴于驅(qū)動(dòng)程序的具體實(shí)現(xiàn)。Linux 允許應(yīng)用程序多次打開(kāi)或者關(guān)閉與聲卡對(duì)應(yīng)的設(shè)備文件,從而能夠很方便地在放音狀態(tài)和錄音狀態(tài)之間進(jìn)行切換。
② 如果有需要,設(shè)置緩沖區(qū)大小
??????? 運(yùn)行在Linux 內(nèi)核中的聲卡驅(qū)動(dòng)程序?qū)iT(mén)維護(hù)了一個(gè)緩沖區(qū),其大小會(huì)影響到放音和錄音時(shí)的效果,使用ioctl()系統(tǒng)調(diào)用可以對(duì)它的尺寸進(jìn)行恰當(dāng)?shù)脑O(shè)置。調(diào)節(jié)驅(qū)動(dòng)程序中緩沖區(qū)大小的操作不是必須的,如果沒(méi)有特殊的要求,一般采用默認(rèn)的緩沖區(qū)大小也就可以了。如果想設(shè)置緩沖區(qū)的大小,則通常應(yīng)緊跟在設(shè)備
文件打開(kāi)之后,這是因?yàn)閷?duì)聲卡的其它操作有可能會(huì)導(dǎo)致驅(qū)動(dòng)程序無(wú)法再修改其緩沖區(qū)的大小。
③ 設(shè)置聲道(channel)數(shù)量
?????? 根據(jù)硬件設(shè)備和驅(qū)動(dòng)程序的具體情況,可以設(shè)置為單聲道或者立體聲。
④ 設(shè)置采樣格式和采樣頻率
?????? 采樣格式包括AFMT_U8(無(wú)符號(hào)8 位)、AFMT_S8(有符號(hào)8 位)、AFMT_U16_LE(小端模式,無(wú)符號(hào)16 位)、AFMT_U16_BE(大端模式,無(wú)符號(hào)16 位)、AFMT_MPEG、AFMT_AC3 等。使用SNDCTL_DSP_SETFMT IO 控制命令可以設(shè)置采樣格式。對(duì)于大多數(shù)聲卡來(lái)說(shuō),其支持的采樣頻率范圍一般為5kHz 到44.1kHz 或者48kHz,但并不意味著該范圍內(nèi)的所有連續(xù)頻率都會(huì)被硬件支持,在Linux 下進(jìn)行音頻編程時(shí)最常用到的幾種采樣頻率是11025Hz、16000Hz、22050Hz、32000Hz 和44100Hz。使用SNDCTL_DSP_SPEED IO 控制命令可以設(shè)置采樣頻率。
⑤ 讀寫(xiě)/dev/dsp 實(shí)現(xiàn)播放或錄音
????? 下面代碼實(shí)現(xiàn)了利用/dev/dsp 接口進(jìn)行聲音錄制和播放的過(guò)程,它的功能是先錄制幾秒鐘音頻數(shù)據(jù),將其存放在內(nèi)存緩沖區(qū)中,然后再進(jìn)行放音。
?
[cpp]?view plain?copy
#include???
#include???
#include???
#include???
#include???
#include???
#include???
#define?LENGTH?3???/*?存儲(chǔ)秒數(shù)?*/??
#define?RATE?8000??/*?采樣頻率?*/??
#define?SIZE?8?????/*?量化位數(shù)?*/??
#define?CHANNELS?1?/*?聲道數(shù)目?*/??
/*?用于保存數(shù)字音頻數(shù)據(jù)的內(nèi)存緩沖區(qū)?*/??
unsigned?char?buf[LENGTH?*RATE?*?SIZE?*?CHANNELS?/?8];??
int?main()??
{??
int?fd;?/*?聲音設(shè)備的文件描述符?*/??
int?arg;?/*?用于ioctl?調(diào)用的參數(shù)?*/??
int?status;?/*?系統(tǒng)調(diào)用的返回值?*/??
/*?打開(kāi)聲音設(shè)備?*/??
fd?=?open("/dev/dsp",?O_RDWR);??
if?(fd?0)??
{??
perror("open?of?/dev/dsp?failed");??
exit(1);??
}??
/*?設(shè)置采樣時(shí)的量化位數(shù)?*/??
arg?=?SIZE;??
status?=?ioctl(fd,?SOUND_PCM_WRITE_BITS,?&arg);??
if?(status?==?-?1)??
perror("SOUND_PCM_WRITE_BITS?ioctl?failed");??
if?(arg?!=?SIZE)??
perror("unable?to?set?sample?size");??
/*?設(shè)置采樣時(shí)的通道數(shù)目?*/??
arg?=?CHANNELS;??
status?=?ioctl(fd,?SOUND_PCM_WRITE_CHANNELS,?&arg);??
if?(status?==?-?1)??
perror("SOUND_PCM_WRITE_CHANNELS?ioctl?failed");??
if?(arg?!=?CHANNELS)??
perror("unable?to?set?number?of?channels");??
/*?設(shè)置采樣率?*/??
arg?=?RATE;??
status?=?ioctl(fd,?SOUND_PCM_WRITE_RATE,?&arg);??
if?(status?==?-?1)??
perror("SOUND_PCM_WRITE_WRITE?ioctl?failed");??
/*?循環(huán),直到按下Control-C?*/??
while?(1)??
{??
printf("Say?something: ");??
status?=?read(fd,?buf,?sizeof(buf));?/*?錄音?*/??
if?(status?!=?sizeof(buf))??
perror("read?wrong?number?of?bytes");??
printf("You?said: ");??
status?=?write(fd,?buf,?sizeof(buf));?/*?放音?*/??
if?(status?!=?sizeof(buf))??
perror("wrote?wrong?number?of?bytes");??
/*?在繼續(xù)錄音前等待放音結(jié)束?*/??
status?=?ioctl(fd,?SOUND_PCM_SYNC,?0);??
if?(status?==?-?1)??
perror("SOUND_PCM_SYNC?ioctl?failed");??
}??
}??
2、mixer 編程
?????? 聲卡上的混音器由多個(gè)混音通道組成,它們可以通過(guò)驅(qū)動(dòng)程序提供的設(shè)備文件/dev/mixer 進(jìn)行編程。對(duì)混音器的操作一般都通過(guò)ioctl()系統(tǒng)調(diào)用來(lái)完成,所有控制命令都以SOUND_MIXER 或者M(jìn)IXER 開(kāi)頭,下表列出了常用的混音器控制命令。
命 令????????????? 作 用
SOUND_MIXER_VOLUME 主音量調(diào)節(jié)
SOUND_MIXER_BASS 低音控制
SOUND_MIXER_TREBLE 高音控制
SOUND_MIXER_SYNTH FM 合成器
SOUND_MIXER_PCM 主D/A 轉(zhuǎn)換器
SOUND_MIXER_SPEAKER PC 喇叭
SOUND_MIXER_LINE 音頻線輸入
SOUND_MIXER_MIC 麥克風(fēng)輸入
SOUND_MIXER_CD CD 輸入
SOUND_MIXER_IMIX 放音音量
SOUND_MIXER_ALTPCM 從D/A 轉(zhuǎn)換器
SOUND_MIXER_RECLEV 錄音音量
SOUND_MIXER_IGAIN 輸入增益
SOUND_MIXER_OGAIN 輸出增益
SOUND_MIXER_LINE1 聲卡的第1 輸入
SOUND_MIXER_LINE2 聲卡的第2 輸入
SOUND_MIXER_LINE3 聲卡的第3 輸入
?????? 對(duì)聲卡的輸入增益和輸出增益進(jìn)行調(diào)節(jié)是混音器的一個(gè)主要作用,目前大部分聲卡采用的是8 位或者16 位的增益控制器,聲卡驅(qū)動(dòng)程序會(huì)將它們變換成百分比的形式,也就是說(shuō)無(wú)論是輸入增益還是輸出增益,其取值范圍都是從0 到100。
? SOUND_MIXER_READ
?????? 在進(jìn)行混音器編程時(shí),可以使用 SOUND_MIXER_READ 宏來(lái)讀取混音通道的增益大小,例如如下代碼可以獲得麥克風(fēng)的輸入增益:
ioctl(fd, SOUND_MIXER_READ(SOUND_MIXER_MIC), &vol);
?????? 對(duì)于只有一個(gè)混音通道的單聲道設(shè)備來(lái)說(shuō),返回的增益大小保存在低位字節(jié)中。而對(duì)于支持多個(gè)混音通道的雙聲道設(shè)備來(lái)說(shuō),返回的增益大小實(shí)際上包括兩個(gè)部分,分別代表左、右兩個(gè)聲道的值,其中低位字節(jié)保存左聲道的音量,而高位字節(jié)則保存右聲道的音量。下面的代碼可以從返回值中依次提取左右聲道的增益大?。?br />
int left, right;
left = vol & 0xff;
right = (vol & 0xff00) >> 8;
? SOUND_MIXER_WRITE
?????? 如果想設(shè)置混音通道的增益大小,則可以通過(guò)SOUND_MIXER_WRITE 宏來(lái)實(shí)現(xiàn),例如下面的語(yǔ)句可以用來(lái)設(shè)置麥克風(fēng)的輸入增益:
vol = (right << 8) + left;
ioctl(fd, SOUND_MIXER_WRITE(SOUND_MIXER_MIC), &vol);
? 查詢Mixer信息
?????? 聲卡驅(qū)動(dòng)程序提供了多個(gè)ioctl()系統(tǒng)調(diào)用來(lái)獲得混音器的信息,它們通常返回一個(gè)整型的位掩碼,其中每一位分別代表一個(gè)特定的混音通道,如果相應(yīng)的位為1,則說(shuō)明與之對(duì)應(yīng)的混音通道是可用的。通過(guò) SOUND_MIXER_READ_DEVMASK 返回的位掩碼查詢出能夠被聲卡支持的每一個(gè)混音通道,而通過(guò)SOUND_MIXER_READ_RECMAS 返回的位掩碼則可以查詢出能夠被當(dāng)作錄音源的每一個(gè)通道。例如,如下代碼可用來(lái)檢查CD 輸入是否是一個(gè)有效的混音通道:
ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask);
if (devmask & SOUND_MIXER_CD)
?? printf("The CD input is supported");
如下代碼可用來(lái)檢查CD 輸入是否是一個(gè)有效的錄音源:
ioctl(fd, SOUND_MIXER_READ_RECMASK, &recmask);
if (recmask & SOUND_MIXER_CD)
?? printf("The CD input can be a recording source");
大多數(shù)聲卡提供了多個(gè)錄音源,通過(guò) SOUND_MIXER_READ_RECSRC 可以查詢出當(dāng)前正在使用的錄音源,同一時(shí)刻可使用2個(gè)或2個(gè)以上的錄音源,具體由聲卡硬件本身決定。相應(yīng)地,使用 SOUND_MIXER_WRITE_RECSRC可以設(shè)置聲卡當(dāng)前使用的錄音源,如下代碼可以將CD輸入作為聲卡的錄音源使用:
devmask = SOUND_MIXER_CD;
ioctl(fd, SOUND_MIXER_WRITE_RECSRC, &devmask);
此外,所有的混音通道都有單聲道和雙聲道的區(qū)別,如果需要知道哪些混音通道提供了對(duì)立體聲的支持,可以通過(guò)SOUND_MIXER_READ_STEREODEVS 來(lái)獲得。
以下代碼實(shí)現(xiàn)了利用/dev/mixer 接口對(duì)混音器進(jìn)行編程的過(guò)程,該程序可對(duì)各種混音通道的增益進(jìn)行調(diào)節(jié)。
[cpp]?view plain?copy
#include???
#include???
#include???
#include???
#include???
#include???
/*?用來(lái)存儲(chǔ)所有可用混音設(shè)備的名稱?*/??
const?char?*sound_device_names[]?=?SOUND_DEVICE_NAMES;??
int?fd;?/*?混音設(shè)備所對(duì)應(yīng)的文件描述符?*/??
int?devmask,?stereodevs;?/*?混音器信息對(duì)應(yīng)的bit?掩碼?*/??
char?*name;??
/*?顯示命令的使用方法及所有可用的混音設(shè)備?*/??
void?usage()??
{??
int?i;??
fprintf(stderr,?"usage:?%s??? "??
"%s?? ""Where??is?one?of: ",?name,?name);??
for?(i?=?0;?i?
if?((1?<
/*?只顯示有效的混音設(shè)備?*/??
fprintf(stderr,?"%s?",?sound_device_names[i]);??
fprintf(stderr,?" ");??
exit(1);??
}??
int?main(int?argc,?char?*argv[])??
{??
int?left,?right,?level;?/*?增益設(shè)置?*/??
int?status;?/*?系統(tǒng)調(diào)用的返回值?*/??
int?device;?/*?選用的混音設(shè)備?*/??
char?*dev;?/*?混音設(shè)備的名稱?*/??
int?i;??
name?=?argv[0];??
/*?以只讀方式打開(kāi)混音設(shè)備?*/??
fd?=?open("/dev/mixer",?O_RDONLY);??
if?(fd?==?-?1)??
{??
perror("unable?to?open?/dev/mixer");??
exit(1);??
}??
/*?獲得所需要的信息?*/??
status?=?ioctl(fd,?SOUND_MIXER_READ_DEVMASK,?&devmask);??
if?(status?==?-?1)??
perror("SOUND_MIXER_READ_DEVMASK?ioctl?failed");??
status?=?ioctl(fd,?SOUND_MIXER_READ_STEREODEVS,?&stereodevs);??
if?(status?==?-?1)??
perror("SOUND_MIXER_READ_STEREODEVS?ioctl?failed");??
/*?檢查用戶輸入?*/??
if?(argc?!=?3?&&?argc?!=?4)??
usage();??
/*?保存用戶輸入的混音器名稱?*/??
dev?=?argv[1];??
/*?確定即將用到的混音設(shè)備?*/??
for?(i?=?0;?i?
if?(((1?<
break;??
if?(i?==?SOUND_MIXER_NRDEVICES)??
{??
/*?沒(méi)有找到匹配項(xiàng)?*/??
fprintf(stderr,?"%s?is?not?a?valid?mixer?device ",?dev);??
usage();??
}??
/*?查找到有效的混音設(shè)備?*/??
device?=?i;??
/*?獲取增益值?*/??
if?(argc?==?4)??
{??
/*?左、右聲道均給定?*/??
left?=?atoi(argv[2]);??
right?=?atoi(argv[3]);??
}??
else??
{??
/*?左、右聲道設(shè)為相等?*/??
left?=?atoi(argv[2]);??
right?=?atoi(argv[2]);??
}??
/*?對(duì)非立體聲設(shè)備給出警告信息?*/??
if?((left?!=?right)?&&?!((1?<
{??
fprintf(stderr,?"warning:?%s?is?not?a?stereo?device ",?dev);??
}??
/*?將兩個(gè)聲道的值合到同一變量中?*/??
level?=?(right?<8)?+?left;??
/*?設(shè)置增益?*/??
status?=?ioctl(fd,?MIXER_WRITE(device),?&level);??
if?(status?==?-?1)??
{??
perror("MIXER_WRITE?ioctl?failed");??
exit(1);??
}??
/*?獲得從驅(qū)動(dòng)返回的左右聲道的增益?*/??
left?=?level?&0xff;??
right?=?(level?&0xff00)?>>?8;??
/*?顯示實(shí)際設(shè)置的增益?*/??
fprintf(stderr,?"%s?gain?set?to?%d%%?/?%d%% ",?dev,?left,?right);??
/*?關(guān)閉混音設(shè)備?*/??
close(fd);??
return?0;??
}??
????? 編譯上述程序?yàn)榭蓤?zhí)行文件mixer,執(zhí)行./mixer 或./mixer 可設(shè)置增益,device 可以是vol、pcm、speaker、line、mic、cd、igain、line1、phin、video。
?
評(píng)論