當(dāng)前位置:首頁(yè) > 嵌入式 > 嵌入式軟件
[導(dǎo)讀]在Linux中,先后出現(xiàn)了音頻設(shè)備的兩種框架OSS和ALSA,本節(jié)將在介紹數(shù)字音頻設(shè)備及音頻設(shè)備硬件接口的基礎(chǔ)上,展現(xiàn)OSS和ALSA驅(qū)動(dòng)的結(jié)構(gòu)。

在Linux中,先后出現(xiàn)了音頻設(shè)備的兩種框架OSS和ALSA,本節(jié)將在介紹數(shù)字音頻設(shè)備及音頻設(shè)備硬件接口的基礎(chǔ)上,展現(xiàn)OSS和ALSA驅(qū)動(dòng)的結(jié)構(gòu)。

17.1~17.2節(jié)講解了音頻設(shè)備及PCM、IIS和AC97硬件接口。

17.3節(jié)闡述了Linux OSS音頻設(shè)備驅(qū)動(dòng)的組成、mixer接口、dsp接口及用戶空間編程方法。

17.4節(jié)闡述了Linux ALSA音頻設(shè)備驅(qū)動(dòng)的組成、card和組件管理、PCM設(shè)備、control接口、AC97 API及用戶空間編程方法。

17.5節(jié)以S3C2410通過(guò)IIS接口外接UDA1341編解碼器的實(shí)例講解了OSS驅(qū)動(dòng)。

17.6節(jié)以PXA255通過(guò)AC97接口外接AC97 編解碼器的實(shí)例講解了ALSA驅(qū)動(dòng)。

17.1數(shù)字音頻設(shè)備

目前,手機(jī)、PDA、MP3等許多嵌入式設(shè)備中包含了數(shù)字音頻設(shè)備,一個(gè)典型的數(shù)字音頻系統(tǒng)的電路組成如圖17.1所示。圖17.1中的嵌入式微控制器 /DSP中集成了PCM、IIS或AC97音頻接口,通過(guò)這些接口連接外部的音頻編解碼器即可實(shí)現(xiàn)聲音的AD和DA轉(zhuǎn)換,圖中的功放完成模擬信號(hào)的放大功能。

圖17.1 典型的數(shù)字音頻系統(tǒng)電路

音頻編解碼器是數(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)行分析,量化精度越高,聲音就越逼真。

17.2音頻設(shè)備硬件接口

17.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ì)列。

17.2.2 IIS接口

IIS 接口(Inter-IC Sound)在20世紀(jì)80年代首先被飛利浦用于消費(fèi)音頻,并在一個(gè)稱為L(zhǎng)RCLK(Left/Right CLOCK)的信號(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ì)列也是可能的。

17.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ù)。一般來(lái)說(shuō),AC’97 編解碼器采用TQFP48封裝。

PCM、IIS和AC97各有其優(yōu)點(diǎn)和應(yīng)用范圍,例如在CD、MD、MP3隨身聽(tīng)多采用IIS接口,移動(dòng)電話會(huì)采用PCM接口,具有音頻功能的PDA則多使用和PC一樣的AC‘97編碼格式。

17.3 Linux OSS音頻設(shè)備驅(qū)動(dòng)

17.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ù)。

17.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控制命令,代碼清單17.1給出了1個(gè)ioctl()的范例。

代碼清單17.1 mixer()接口ioctl()函數(shù)范例

1 static int mixdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)

2 {

3 。。。

4 switch (cmd)

5 {

6 case SOUND_MIXER_READ_MIC:

7 。。。

8 case SOUND_MIXER_WRITE_MIC:

9 。。。

10 case SOUND_MIXER_WRITE_RECSRC:

11 。。。

12 case SOUND_MIXER_WRITE_MUTE:

13 。。。

14 }

15 //其它命令

16 return mixer_ioctl(codec, cmd, arg);

17 }

17.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í)候,會(huì)調(diào)用如代碼清單17.2。

代碼清單

17.2 OSS驅(qū)動(dòng)初始化注冊(cè)dsp和mixer設(shè)備

1 static int xxx_init(void)

2 {

3 struct xxx_state *s = &xxx_state;

4 。。。

5 //注冊(cè)dsp設(shè)備

6 if ((audio_dev_dsp = register_sound_dsp(&xxx_audio_fops, - 1)) 《 0)

7 goto err_dev1;

8 //設(shè)備mixer設(shè)備

9 if ((audio_dev_mixer = register_sound_mixer(&xxx_mixer_fops, - 1)) 《 0)

10 goto err_dev2;

11 。。。

12 }

13

14 void __exit xxx_exit(void)

15 {

16 //注銷dsp和mixer設(shè)備接口

17 unregister_sound_dsp(audio_dev_dsp);

18 unregister_sound_mixer(audio_dev_mixer);

19 。。。

20 }

根據(jù)17.3.2和17.3.3節(jié)的分析,可以畫(huà)出一個(gè)Linux OSS驅(qū)動(dòng)結(jié)構(gòu)的簡(jiǎn)圖。

17.3.4 OSS用戶空間編程

1、DSP編程

對(duì)OSS驅(qū)動(dòng)聲卡的編程使用Linux文件接口函數(shù),如圖17.5,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維護(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)播放或錄音。

代碼清單17.3的程序?qū)崿F(xiàn)了利用/dev/dsp接口進(jìn)行聲音錄制和播放的過(guò)程,它的功能是先錄制幾秒鐘音頻數(shù)據(jù),將其存放在內(nèi)存緩沖區(qū)中,然后再進(jìn)行放音。

代碼清單17.3 OSS DSP接口應(yīng)用編程范例

1 #include

2 #include

3 #include

4 #include

5 #include

6 #include

7 #include

8 #define LENGTH 3 /* 存儲(chǔ)秒數(shù) */

9 #define RATE 8000 /* 采樣頻率 */

10 #define SIZE 8 /* 量化位數(shù) */

11 #define CHANNELS 1 /* 聲道數(shù)目 */

12 /* 用于保存數(shù)字音頻數(shù)據(jù)的內(nèi)存緩沖區(qū) */

13 unsigned char buf[LENGTH *RATE * SIZE * CHANNELS / 8];

14 int main()

15 {

16 int fd; /* 聲音設(shè)備的文件描述符 */

17 int arg; /* 用于ioctl調(diào)用的參數(shù) */

18 int status; /* 系統(tǒng)調(diào)用的返回值 */

19 /* 打開(kāi)聲音設(shè)備 */

20 fd = open(“/dev/dsp”, O_RDWR);

21 if (fd 《 0)

22 {

23 perror(“open of /dev/dsp failed”);

24 exit(1);

25 }

26 /* 設(shè)置采樣時(shí)的量化位數(shù) */

27 arg = SIZE;

28 status = ioctl(fd, SOUND_PCM_WRITE_BITS, &arg);

29 if (status == - 1)

30 perror(“SOUND_PCM_WRITE_BITS ioctl failed”);

31 if (arg != SIZE)

32 perror(“unable to set sample size”);

33 /* 設(shè)置采樣時(shí)的通道數(shù)目 */

34 arg = CHANNELS;

35 status = ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &arg);

36 if (status == - 1)

37 perror(“SOUND_PCM_WRITE_CHANNELS ioctl failed”);

38 if (arg != CHANNELS)

39 perror(“unable to set number of channels”);

40 /* 設(shè)置采樣率 */

41 arg = RATE;

42 status = ioctl(fd, SOUND_PCM_WRITE_RATE, &arg);

43 if (status == - 1)

44 perror(“SOUND_PCM_WRITE_WRITE ioctl failed”);

45 /* 循環(huán),直到按下Control-C */

46 while (1)

47 {

48 printf(“Say something:/n”);

49 status = read(fd, buf, sizeof(buf)); /* 錄音 */

50 if (status != sizeof(buf))

51 perror(“read wrong number of bytes”);

52 printf(“You said:/n”);

53 status = write(fd, buf, sizeof(buf)); /* 放音 */

54 if (status != sizeof(buf))

55 perror(“wrote wrong number of bytes”);

56 /* 在繼續(xù)錄音前等待放音結(jié)束 */

57 status = ioctl(fd, SOUND_PCM_SYNC, 0);

58 if (status == - 1)

59 perror(“SOUND_PCM_SYNC ioctl failed”);

60 }

61 }

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)頭,表17.1列出了常用的混音器控制命令。

表17.1 混音器常用命令

命 令 作 用

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é)則保存右聲道的音量。下面的代碼可以從返回值中依次提取左右聲道的增益大小:

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)獲得。

代碼清單17.4的程序?qū)崿F(xiàn)了利用/dev/mixer接口對(duì)混音器進(jìn)行編程的過(guò)程,該程序可對(duì)各種混音通道的增益進(jìn)行調(diào)節(jié)。

代碼清單17.4 OSS mixer接口應(yīng)用編程范例

1 #include

2 #include

3 #include

4 #include

5 #include

6 #include

7 /* 用來(lái)存儲(chǔ)所有可用混音設(shè)備的名稱 */

8 const char *sound_device_names[] = SOUND_DEVICE_NAMES;

9 int fd; /* 混音設(shè)備所對(duì)應(yīng)的文件描述符 */

10 int devmask, stereodevs; /* 混音器信息對(duì)應(yīng)的bit掩碼 */

11 char *name;

12 /* 顯示命令的使用方法及所有可用的混音設(shè)備 */

13 void usage()

14 {

15 int i;

16 fprintf(stderr, “usage: %s /n”

17 “%s /n/n”“Where is one of:/n”, name, name);

18 for (i = 0; i 《 SOUND_MIXER_NRDEVICES; i++)

19 if ((1 《《 i) &devmask)

20 /* 只顯示有效的混音設(shè)備 */

21 fprintf(stderr, “%s ”, sound_device_names[i]);

22 fprintf(stderr, “/n”);

23 exit(1);

24 }

25

26 int main(int argc, char *argv[])

27 {

28 int left, right, level; /* 增益設(shè)置 */

29 int status; /* 系統(tǒng)調(diào)用的返回值 */

30 int device; /* 選用的混音設(shè)備 */

31 char *dev; /* 混音設(shè)備的名稱 */

32 int i;

33 name = argv[0];

34 /* 以只讀方式打開(kāi)混音設(shè)備 */

35 fd = open(“/dev/mixer”, O_RDONLY);

36 if (fd == - 1)

37 {

38 perror(“unable to open /dev/mixer”);

39 exit(1);

40 }

41

42 /* 獲得所需要的信息 */

43 status = ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask);

44 if (status == - 1)

45 perror(“SOUND_MIXER_READ_DEVMASK ioctl failed”);

46 status = ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs);

47 if (status == - 1)

48 perror(“SOUND_MIXER_READ_STEREODEVS ioctl failed”);

49 /* 檢查用戶輸入 */

50 if (argc != 3 && argc != 4)

51 usage();

52 /* 保存用戶輸入的混音器名稱 */

53 dev = argv[1];

54 /* 確定即將用到的混音設(shè)備 */

55 for (i = 0; i 《 SOUND_MIXER_NRDEVICES; i++)

56 if (((1 《《 i) &devmask) && !strcmp(dev, sound_device_names[i]))

57 break;

58 if (i == SOUND_MIXER_NRDEVICES)

59 {

60 /* 沒(méi)有找到匹配項(xiàng) */

61 fprintf(stderr, “%s is not a valid mixer device/n”, dev);

62 usage();

63 }

64 /* 查找到有效的混音設(shè)備 */

65 device = i;

66 /* 獲取增益值 */

67 if (argc == 4)

68 {

69 /* 左、右聲道均給定 */

70 left = atoi(argv[2]);

71 right = atoi(argv[3]);

72 }

73 else

74 {

75 /* 左、右聲道設(shè)為相等 */

76 left = atoi(argv[2]);

77 right = atoi(argv[2]);

78 }

79

80 /* 對(duì)非立體聲設(shè)備給出警告信息 */

81 if ((left != right) && !((1 《《 i) &stereodevs))

82 {

83 fprintf(stderr, “warning: %s is not a stereo device/n”, dev);

84 }

85

86 /* 將兩個(gè)聲道的值合到同一變量中 */

87 level = (right 《《 8) + left;

88

89 /* 設(shè)置增益 */

90 status = ioctl(fd, MIXER_WRITE(device), &level);

91 if (status == - 1)

92 {

93 perror(“MIXER_WRITE ioctl failed”);

94 exit(1);

95 }

96 /* 獲得從驅(qū)動(dòng)返回的左右聲道的增益 */

97 left = level &0xff;

98 right = (level &0xff00) 》》 8;

99 /* 顯示實(shí)際設(shè)置的增益 */

100 fprintf(stderr, “%s gain set to %d%% / %d%%/n”, dev, left, right);

101 /* 關(guān)閉混音設(shè)備 */

102 close(fd);

103 return 0;

104 }

編譯上述程序?yàn)榭蓤?zhí)行文件mixer,執(zhí)行。/mixer 或。/mixer 可設(shè)置增益,device可以是vol、pcm、speaker、line、mic、cd、igain、line1、 phin、video。

17.4 Linux ALSA音頻設(shè)備驅(qū)動(dòng)

17.4.1 ALSA的組成

雖然OSS已經(jīng)非常成熟,但它畢竟是一個(gè)沒(méi)有完全開(kāi)放源代碼的商業(yè)產(chǎn)品,而ALSA (Advanced Linux Sound Architecture)恰好彌補(bǔ)了這一空白,它符合GPL,是在Linux下進(jìn)行音頻編程時(shí)另一種可供選擇的聲卡驅(qū)動(dòng)體系結(jié)構(gòu),其官方網(wǎng)站為 http://www.alsa-project.org/。ALSA除了像OSS那樣提供了一組內(nèi)核驅(qū)動(dòng)程序模塊之外,還專門為簡(jiǎn)化應(yīng)用程序的編寫(xiě)提供了相應(yīng)的函數(shù)庫(kù),與OSS提供的基于ioctl的原始編程接口相比,ALSA函數(shù)庫(kù)使用起來(lái)要更加方便一些。ALSA的主要特點(diǎn)有:

? 支持多種聲卡設(shè)備

? 模塊化的內(nèi)核驅(qū)動(dòng)程序

? 支持SMP和多線程

? 提供應(yīng)用開(kāi)發(fā)函數(shù)庫(kù)(alsa-lib)以簡(jiǎn)化應(yīng)用程序開(kāi)發(fā)

? 支持OSS API,兼容OSS應(yīng)用程序

ALSA 具有更加友好的編程接口,并且完全兼容于OSS,對(duì)應(yīng)用程序員來(lái)講無(wú)疑是一個(gè)更佳的選擇。ALSA系統(tǒng)包括驅(qū)動(dòng)包alsa-driver、開(kāi)發(fā)包 alsa-libs、開(kāi)發(fā)包插件alsa-libplugins、設(shè)置管理工具包alsa-utils、其他聲音相關(guān)處理小程序包alsa-tools、特殊音頻固件支持包alsa- firmware、OSS接口兼容模擬層工具alsa-oss共7個(gè)子項(xiàng)目,其中只有驅(qū)動(dòng)包是必需的。

alsa- driver指內(nèi)核驅(qū)動(dòng)程序,包括硬件相關(guān)的代碼和一些公共代碼,非常龐大,代碼總量達(dá)數(shù)十萬(wàn)行;alsa-libs指用戶空間的函數(shù)庫(kù),提供給應(yīng)用程序使用,應(yīng)用程序應(yīng)包含頭文件asoundlib.h,并使用共享庫(kù)libasound.so;alsa-utils包含一些基于ALSA的用于控制聲卡的應(yīng)用程序,如alsaconf(偵測(cè)系統(tǒng)中聲卡并寫(xiě)一個(gè)適合的ALSA配置文件)、alsactl(控制ALSA聲卡驅(qū)動(dòng)的高級(jí)設(shè)置)、 alsamixer(基于ncurses的混音器程序)、amidi(用于讀寫(xiě)ALSA RawMIDI)、amixer(ALSA聲卡混音器的命令行控制)、aplay(基于命令行的聲音文件播放)、arecord(基于命令行的聲音文件錄制)等。

目前ALSA內(nèi)核提供給用戶空間的接口有:

? 信息接口(Information Interface,/proc/asound)

? 控制接口(Control Interface,/dev/snd/controlCX)

? 混音器接口(Mixer Interface,/dev/snd/mixerCXDX)

? PCM接口(PCM Interface,/dev/snd/pcmCXDX)

? Raw迷笛接口(Raw MIDI Interface,/dev/snd/midiCXDX)

? 音序器接口(Sequencer Interface,/dev/snd/seq)

? 定時(shí)器接口(Timer Interface,/dev/snd/timer)

和OSS類似,上述接口也以文件的方式被提供,不同的是這些接口被提供給alsa-lib使用,而不是直接給應(yīng)用程序使用的。應(yīng)用程序最好使用alsa-lib,或者更高級(jí)的接口,比如jack提供的接口。

17.4.1 card和組件管理

對(duì)于每個(gè)聲卡而言,必須創(chuàng)建1個(gè)“card”實(shí)例。card是聲卡的“總部”,它管理這個(gè)聲卡上的所有設(shè)備(組件),如PCM、mixers、MIDI、synthesizer等。因此,card和組件是ALSA聲卡驅(qū)動(dòng)中的主要組成元素。

1、創(chuàng)建card

struct snd_card *snd_card_new(int idx, const char *xid,

struct module *module, int extra_size);

idx是card索引號(hào)、xid是標(biāo)識(shí)字符串、module一般為THIS_MODULE,extra_size是要分配的額外數(shù)據(jù)的大小,分配的extra_size大小的內(nèi)存將作為card-》private_data。

2、創(chuàng)建組件

int snd_device_new(struct snd_card *card, snd_device_type_t type,

void *device_data, struct snd_device_ops *ops);

當(dāng) card被創(chuàng)建后,設(shè)備(組件)能夠被創(chuàng)建并關(guān)聯(lián)于該card。第1個(gè)參數(shù)是snd_card_new()創(chuàng)建的card指針,第2個(gè)參數(shù)type 指的是device-level即設(shè)備類型,形式為SNDRV_DEV_XXX,包括SNDRV_DEV_CODEC、 SNDRV_DEV_CONTROL、SNDRV_DEV_PCM、SNDRV_DEV_RAWMIDI等,用戶自定義設(shè)備的device-level是 SNDRV_DEV_LOWLEVEL,ops參數(shù)是1個(gè)函數(shù)集(定義為snd_device_ops結(jié)構(gòu)體)的指針,device_data是設(shè)備數(shù)據(jù)指針,注意函數(shù)snd_device_new()本身不會(huì)分配設(shè)備數(shù)據(jù)的內(nèi)存,因此應(yīng)事先分配。

3、組件釋放

每個(gè)ALSA預(yù)定義的組件在構(gòu)造時(shí)需調(diào)用snd_device_new(),而每個(gè)組件的析構(gòu)方法則在函數(shù)集中被包含。對(duì)于PCM、AC97此類預(yù)定義組件,我們不需關(guān)心它們的析構(gòu),而對(duì)于自定義的組件,則需要填充snd_device_ops中的析構(gòu)函數(shù)指針dev_free,這樣,當(dāng) snd_card_free()被調(diào)用時(shí),組件將自動(dòng)被釋放。

4、芯片特定的數(shù)據(jù)(Chip-Specific Data)

芯片特定的數(shù)據(jù)一般以struct xxxchip結(jié)構(gòu)體形式組織,這個(gè)結(jié)構(gòu)體中包含芯片相關(guān)的I/O端口地址、資源指針、中斷號(hào)等,其意義等同于字符設(shè)備驅(qū)動(dòng)中的 file-》private_data。定義芯片特定的數(shù)據(jù)主要有2種方法,一種方法是將sizeof(struct xxxchip)傳入snd_card_new()的extra_size參數(shù),它將自動(dòng)成員snd_card的private_data成員,如代碼清單17.5;另一種方法是在snd_card_new()傳入給extra_size參數(shù)0,再分配sizeof(struct xxxchip)的內(nèi)存,將分配內(nèi)存的地址傳入snd_device_new()的device_data的參數(shù),如代碼清單17.6。

代碼清單17.5 創(chuàng)建芯片特定的數(shù)據(jù)方法1

1 struct xxxchip //芯片特定的數(shù)據(jù)結(jié)構(gòu)體

2 {

3 。。。

4 };

5 card = snd_card_new(index, id, THIS_MODULE, sizeof(struct

6 xxxchip)); //創(chuàng)建聲卡并申請(qǐng)xxx_chi內(nèi)存作為card-》 private_data

7 struct xxxchip *chip = card-》private_data;

代碼清單17.6 創(chuàng)建芯片特定的數(shù)據(jù)方法2

1 struct snd_card *card;

2 struct xxxchip *chip;

3 //使用0作為第4個(gè)參數(shù),并動(dòng)態(tài)分配xxx_chip的內(nèi)存:

4 card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);

5 。。。

6 chip = kzalloc(sizeof(*chip), GFP_KERNEL);

7 //在xxxchip結(jié)構(gòu)體中,應(yīng)該包括聲卡指針:

8 struct xxxchip

9 {

10 struct snd_card *card;

11 。。。

12 };

13 //并將其card成員賦值為snd_card_new()創(chuàng)建的card指針:

14 chip-》card = card;

15 static struct snd_device_ops ops =

16 {

17 .dev_free = snd_xxx_chip_dev_free, //組件析構(gòu)

18 };

19 。。。

20 //創(chuàng)建自定義組件

21 snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);

22 //在析構(gòu)函數(shù)中釋放xxxchip內(nèi)存

23 static int snd_xxx_chip_dev_free(struct snd_device *device)

24 {

25 return snd_xxx_chip_free(device-》device_data); //釋放

26 }

5、注冊(cè)/釋放聲卡

當(dāng)snd_card被準(zhǔn)備好以后,可使用snd_card_register()函數(shù)注冊(cè)這個(gè)聲卡:

int snd_card_register(struct snd_card *card)

對(duì)應(yīng)的snd_card_free()完成相反的功能:

int snd_card_free(struct snd_card *card);

4、put()函數(shù)

put()用于從用戶空間寫(xiě)入值,如果值被改變,該函數(shù)返回1,否則返回0;如果發(fā)生錯(cuò)誤,該函數(shù)返回1個(gè)錯(cuò)誤碼。代碼清單17.22給出了1個(gè)put()函數(shù)的范例。

代碼清單17.22 snd_ctl_elem_info結(jié)構(gòu)體中put()函數(shù)范例

1 static int snd_xxxctl_put(struct snd_kcontrol *kcontrol, struct

2 snd_ctl_elem_value *ucontrol)

3 {

4 //從snd_kcontrol獲得xxxchip指針

5 struct xxxchip *chip = snd_kcontrol_chip(kcontrol);

6 int changed = 0;//缺省返回值為0

7 //值被改變

8 if (chip-》current_value != ucontrol-》value.integer.value[0])

9 {

10 change_current_value(chip, ucontrol-》value.integer.value[0]);

11 changed = 1;//返回值為1

12 }

13 return changed;

14 }

對(duì)于get()和put()函數(shù)而言,如果control有多于1個(gè)元素,即count》1,則每個(gè)元素都需要被返回或?qū)懭搿?/p>

5、構(gòu)造control

當(dāng)所有事情準(zhǔn)備好后,我們需要?jiǎng)?chuàng)建1個(gè)control,調(diào)用snd_ctl_add()和snd_ctl_new1()這2個(gè)函數(shù)來(lái)完成,這2個(gè)函數(shù)的原型為:

int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol);

struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,

void *private_data);

snd_ctl_new1()函數(shù)用于創(chuàng)建1個(gè)snd_kcontrol并返回其指針,snd_ctl_add()函數(shù)用于將創(chuàng)建的snd_kcontrol添加到對(duì)應(yīng)的card中。

6、變更通知

如果驅(qū)動(dòng)中需要在中斷服務(wù)程序中改變或更新1個(gè)control,可以調(diào)用snd_ctl_notify()函數(shù),此函數(shù)原型為:

void snd_ctl_notify(struct snd_card *card, unsigned int mask, struct snd_ctl_elem_id *id);

該函數(shù)的第2個(gè)參數(shù)為事件掩碼(event-mask),第3個(gè)參數(shù)為該通知的control元素id指針。

例如,如下語(yǔ)句定義的事件掩碼SNDRV_CTL_EVENT_MASK_VALUE意味著control值的改變被通知:

snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, id_pointer);

17.4.4 AC97 API接口

ALSA AC97編解碼層被很好地定義,利用它,驅(qū)動(dòng)工程師只需編寫(xiě)少量底層的控制函數(shù)。

1、AC97實(shí)例構(gòu)造

為了創(chuàng)建1個(gè)AC97實(shí)例,首先需要調(diào)用snd_ac97_bus()函數(shù)構(gòu)建AC97總線及其操作,這個(gè)函數(shù)的原型為:

int snd_ac97_bus(struct snd_card *card, int num, struct snd_ac97_bus_ops *ops,

void *private_data, struct snd_ac97_bus **rbus);

該函數(shù)的第3個(gè)參數(shù)ops是1個(gè)snd_ac97_bus_ops結(jié)構(gòu)體,其定義如代碼清單17.23。

代碼清單17.23 snd_ac97_bus_ops結(jié)構(gòu)體

1 struct snd_ac97_bus_ops

2 {

3 void(*reset)(struct snd_ac97 *ac97); //復(fù)位函數(shù)

4 //寫(xiě)入函數(shù)

5 void(*write)(struct snd_ac97 *ac97, unsigned short reg, unsigned short val);

6 //讀取函數(shù)

7 unsigned short(*read)(struct snd_ac97 *ac97, unsigned short reg);

8 void(*wait)(struct snd_ac97 *ac97);

9 void(*init)(struct snd_ac97 *ac97);

10 };

接下來(lái),調(diào)用snd_ac97_mixer()函數(shù)注冊(cè)混音器,這個(gè)函數(shù)的原型為:

int snd_ac97_mixer(struct snd_ac97_bus *bus, struct snd_ac97_template *template, struct snd_ac97 **rac97);

代碼清單17.24演示了AC97實(shí)例的創(chuàng)建過(guò)程。

代碼清單17.24 AC97實(shí)例的創(chuàng)建過(guò)程范例

1 struct snd_ac97_bus *bus;

2 //AC97總線操作

3 static struct snd_ac97_bus_ops ops =

4 {

5 .write = snd_mychip_ac97_write,

6 .read = snd_mychip_ac97_read,

7 };

8 //AC97總線與操作創(chuàng)建

9 snd_ac97_bus(card, 0, &ops, NULL, &bus);

10 //AC97模板

11 struct snd_ac97_template ac97;

12 int err;

13 memset(&ac97, 0, sizeof(ac97));

14 ac97.private_data = chip;//私有數(shù)據(jù)

15 //注冊(cè)混音器

16 snd_ac97_mixer(bus, &ac97, &chip-》ac97);

上述代碼第1行的snd_ac97_bus結(jié)構(gòu)體指針bus的指針被傳入第9行的snd_ac97_bus()函數(shù)并被賦值,chip-》ac97的指針被傳入第16行的snd_ac97_mixer()并被賦值,chip-》ac97將成員新創(chuàng)建AC97實(shí)例的指針。

如果1個(gè)聲卡上包含多個(gè)編解碼器,這種情況下,需要多次調(diào)用snd_ac97_mixer()并對(duì)snd_ac97的num成員(編解碼器序號(hào))賦予相應(yīng)的序號(hào)。驅(qū)動(dòng)中可以為不同的編解碼器編寫(xiě)不同的snd_ac97_bus_ops成員函數(shù)中,或者只是在相同的一套成員函數(shù)中通過(guò)ac97.num獲得序號(hào)后再區(qū)分進(jìn)行具體的操作。

2、snd_ac97_bus_ops成員函數(shù)

snd_ac97_bus_ops結(jié)構(gòu)體中的read()和write()成員函數(shù)完成底層的硬件訪問(wèn),reset()函數(shù)用于復(fù)位編解碼器,wait()函數(shù)用于編解碼器標(biāo)準(zhǔn)初始化過(guò)程中的特定等待,如果芯片要求額外的等待時(shí)間,應(yīng)實(shí)現(xiàn)這個(gè)函數(shù),init()用于完成編解碼器附加的初始化。代碼清單17.25給出了read()和write()函數(shù)的范例。

代碼清單17.25 snd_ac97_bus_ops結(jié)構(gòu)體中read()和write()函數(shù)范例

1 static unsigned short snd_xxxchip_ac97_read(struct snd_ac97 *ac97, unsigned

2 short reg)

3 {

4 struct xxxchip *chip = ac97-》private_data;

5 。。。

6 return the_register_value; //返回寄存器值

7 }

8

9 static void snd_xxxchip_ac97_write(struct snd_ac97 *ac97, unsigned short reg,

10 unsigned short val)

11 {

12 struct xxxchip *chip = ac97-》private_data;

13 。。。

14 // 將被給的寄存器值寫(xiě)入codec

15 }

3、修改寄存器

如果需要在驅(qū)動(dòng)中訪問(wèn)編解碼器,可使用如下函數(shù):

void snd_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short value);

int snd_ac97_update(struct snd_ac97 *ac97, unsigned short reg, unsigned short value);

int snd_ac97_update_bits(struct snd_ac97 *ac97, unsigned short reg, unsigned short mask, unsigned short value);

unsigned short snd_ac97_read(struct snd_ac97 *ac97, unsigned short reg);

snd_ac97_update()與void snd_ac97_write()的區(qū)別在于前者在值已經(jīng)設(shè)置的情況下不會(huì)再設(shè)置,而后者則會(huì)再寫(xiě)一次。snd_ac97_update_bits()用于更新寄存器的某些位,由mask決定。

除此之外,還有1個(gè)函數(shù)可用于設(shè)置采樣率:

int snd_ac97_set_rate(struct snd_ac97 *ac97, int reg, unsigned int rate);

這個(gè)函數(shù)的第2個(gè)參數(shù)reg可以是AC97_PCM_MIC_ADC_RATE、AC97_PCM_FRONT_DAC_RATE、 AC97_PCM_LR_ADC_RATE和AC97_SPDIF,對(duì)于AC97_SPDIF而言,寄存器并非真地被改變了,只是相應(yīng)的IEC958狀態(tài)位將被更新。

4、時(shí)鐘調(diào)整

在一些芯片上,編解碼器的時(shí)鐘不是48000而是使用PCI時(shí)鐘以節(jié)省1個(gè)晶體,在這種情況下,我們應(yīng)該改變bus-》clock為相應(yīng)的值,例如intel8x0和es1968包含時(shí)鐘的自動(dòng)測(cè)量函數(shù)。

5、proc文件

ALSA AC97接口會(huì)創(chuàng)建如/proc/asound/card0/codec97#0/ac97#0-0和ac97#0-0+regs這樣的proc文件,通過(guò)這些文件可以察看編解碼器目前的狀態(tài)和寄存器。

如果1個(gè)chip上有多個(gè)codecs,可多次調(diào)用snd_ac97_mixer()。

17.4.5 ALSA用戶空間編程

ALSA驅(qū)動(dòng)的聲卡在用戶空間不宜直接使用文件接口,而應(yīng)使用alsa-lib,代碼清單17.26給出了基于ALSA音頻驅(qū)動(dòng)的最簡(jiǎn)單的放音應(yīng)用程序。

代碼清單17.26 ALSA用戶空間放音程序

1 #include

2 #include

3 #include

4

5 main(int argc, char *argv[])

6 {

7 int i;

8 int err;

9 short buf[128];

10 snd_pcm_t *playback_handle; //PCM設(shè)備句柄

11 snd_pcm_hw_params_t *hw_params; //硬件信息和PCM流配置

12 //打開(kāi)PCM,最后1個(gè)參數(shù)為0意味著標(biāo)準(zhǔn)配置

13 if ((err = snd_pcm_open(&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0)

14 ) 《 0)

15 {

16 fprintf(stderr, “cannot open audio device %s (%s)/n”, argv[1], snd_strerror

17 (err));

18 exit(1);

19 }

20 //分配snd_pcm_hw_params_t結(jié)構(gòu)體

21 if ((err = snd_pcm_hw_params_malloc(&hw_params)) 《 0)

22 {

23 fprintf(stderr, “cannot allocate hardware parameter structure (%s)/n”,

24 snd_strerror(err));

25 exit(1);

26 }

27 //初始化hw_params

28 if ((err = snd_pcm_hw_params_any(playback_handle, hw_params)) 《 0)

29 {

30 fprintf(stderr, “cannot initialize hardware parameter structure (%s)/n”,

31 snd_strerror(err));

32 exit(1);

33 }

34 //初始化訪問(wèn)權(quán)限

35 if ((err = snd_pcm_hw_params_set_access(playback_handle, hw_params,

36 SND_PCM_ACCESS_RW_INTERLEAVED)) 《 0)

37 {

38 fprintf(stderr, “cannot set access type (%s)/n”, snd_strerror(err));

39 exit(1);

40 }

41 //初始化采樣格式

42 if ((err = snd_pcm_hw_params_set_format(playback_handle, hw_params,

43 SND_PCM_FORMAT_S16_LE)) 《 0)

44 {

45 fprintf(stderr, “cannot set sample format (%s)/n”, snd_strerror(err));

46 exit(1);

47 }

48 //設(shè)置采樣率,如果硬件不支持我們?cè)O(shè)置的采樣率,將使用最接近的

49 if ((err = snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, 44100,

50 0)) 《 0)

51 {

52 fprintf(stderr, “cannot set sample rate (%s)/n”, snd_strerror(err));

53 exit(1);

54 }

55 //設(shè)置通道數(shù)量

56 if ((err = snd_pcm_hw_params_set_channels(playback_handle, hw_params, 2)) 《 0)

57 {

58 fprintf(stderr, “cannot set channel count (%s)/n”, snd_strerror(err));

59 exit(1);

60 }

61 //設(shè)置hw_params

62 if ((err = snd_pcm_hw_params(playback_handle, hw_params)) 《 0)

63 {

64 fprintf(stderr, “cannot set parameters (%s)/n”, snd_strerror(err));

65 exit(1);

66 }

67 //釋放分配的snd_pcm_hw_params_t結(jié)構(gòu)體

68 snd_pcm_hw_params_free(hw_params);

69 //完成硬件參數(shù)設(shè)置,使設(shè)備準(zhǔn)備好

70 if ((err = snd_pcm_prepare(playback_handle)) 《 0)

71 {

72 fprintf(stderr, “cannot prepare audio interface for use (%s)/n”,

73 snd_strerror(err));

74 exit(1);

75 }

76

77 for (i = 0; i 《 10; ++i)

78 {

79 //寫(xiě)音頻數(shù)據(jù)到PCM設(shè)備

80 if ((err = snd_pcm_writei(playback_handle, buf, 128)) != 128)

81 {

82 fprintf(stderr, “write to audio interface failed (%s)/n”, snd_strerror

83 (err));

84 exit(1);

85 }

86 }

87 //關(guān)閉PCM設(shè)備句柄

88 snd_pcm_close(playback_handle);

89 exit(0);

90 }

由上述代碼可以看出,ALSA用戶空間編程的流程與17.3.4節(jié)給出的OSS驅(qū)動(dòng)用戶空間編程的流程基本是一致的,都經(jīng)過(guò)了“打開(kāi)――設(shè)置參數(shù)――讀寫(xiě)音頻數(shù)據(jù)”的過(guò)程,不同在于OSS打開(kāi)的是設(shè)備文件,設(shè)置參數(shù)使用的是Linux ioctl()系統(tǒng)調(diào)用,讀寫(xiě)音頻數(shù)據(jù)使用的是Linux read()、write()文件API,而ALSA則全部使用alsa-lib中的API。

把上述代碼第80行的snd_pcm_writei()函數(shù)替換為snd_pcm_readi()就編程了1個(gè)最簡(jiǎn)單的錄音程序。

代碼清單17.27的程序打開(kāi)1個(gè)音頻接口,配置它為立體聲、16位、44.1khz采樣和基于interleave的讀寫(xiě)。它阻塞等待直接接口準(zhǔn)備好接收放音數(shù)據(jù),這時(shí)候?qū)?shù)據(jù)拷貝到緩沖區(qū)。這種設(shè)計(jì)方法使得程序很容易移植到類似JACK、LADSPA、Coreaudio、VST等callback機(jī)制驅(qū)動(dòng)的系統(tǒng)。

代碼清單17.27 ALSA用戶空間放音程序(基于“中斷”)

1 #include

2 #include

3 #include

4 #include

5 #include

6

7 snd_pcm_t *playback_handle;

8 short buf[4096];

9

10 int playback_callback(snd_pcm_sframes_t nframes)

11 {

12 int err;

13 printf(“playback callback called with %u frames/n”, nframes);

14 /* 填充緩沖區(qū) */

15 if ((err = snd_pcm_writei(playback_handle, buf, nframes)) 《 0)

16 {

17 fprintf(stderr, “write failed (%s)/n”, snd_strerror(err));

18 }

19

20 return err;

21 }

22

23 main(int argc, char *argv[])

24 {

25

26 snd_pcm_hw_params_t *hw_params;

27 snd_pcm_sw_params_t *sw_params;

28 snd_pcm_sframes_t frames_to_deliver;

29 int nfds;

30 int err;

31 struct pollfd *pfds;

32

33 if ((err = snd_pcm_open(&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0)

34 ) 《 0)

35 {

36 fprintf(stderr, “cannot open audio device %s (%s)/n”, argv[1], snd_strerror

37 (err));

38 exit(1);

39 }

40

41 if ((err = snd_pcm_hw_params_malloc(&hw_params)) 《 0)

42 {

43 fprintf(stderr, “cannot allocate hardware parameter structure (%s)/n”,

44 snd_strerror(err));

45 exit(1);

46 }

47

48 if ((err = snd_pcm_hw_params_any(playback_handle, hw_params)) 《 0)

49 {

50 fprintf(stderr, “cannot initialize hardware parameter structure (%s)/n”,

51 snd_strerror(err));

52 exit(1);

53 }

54

55 if ((err = snd_pcm_hw_params_set_access(playback_handle, hw_params,

56 SND_PCM_ACCESS_RW_INTERLEAVED)) 《 0)

57 {

58 fprintf(stderr, “cannot set access type (%s)/n”, snd_strerror(err));

59 exit(1);

60 }

61

62 if ((err = snd_pcm_hw_params_set_format(playback_handle, hw_params,

63 SND_PCM_FORMAT_S16_LE)) 《 0)

64 {

65 fprintf(stderr, “cannot set sample format (%s)/n”, snd_strerror(err));

66 exit(1);

67 }

68

69 if ((err = snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, 44100,

70 0)) 《 0)

71 {

72 fprintf(stderr, “cannot set sample rate (%s)/n”, snd_strerror(err));

73 exit(1);

74 }

75

76 if ((err = snd_pcm_hw_params_set_channels(playback_handle, hw_params, 2)) 《 0)

77 {

78 fprintf(stderr, “cannot set channel count (%s)/n”, snd_strerror(err));

79 exit(1);

80 }

81

82 if ((err = snd_pcm_hw_params(playback_handle, hw_params)) 《 0)

83 {

84 fprintf(stderr, “cannot set parameters (%s)/n”, snd_strerror(err));

85 exit(1);

86 }

87

88 snd_pcm_hw_params_free(hw_params);

89

90 /* 告訴ALSA當(dāng)4096個(gè)以上幀可以傳遞時(shí)喚醒我們 */

91 if ((err = snd_pcm_sw_params_malloc(&sw_params)) 《 0)

92 {

93 fprintf(stderr, “cannot allocate software parameters structure (%s)/n”,

94 snd_strerror(err));

95 exit(1);

96 }

97 if ((err = snd_pcm_sw_params_current(playback_handle, sw_params)) 《 0)

98 {

99 fprintf(stderr, “cannot initialize software parameters structure (%s)/n”,

100 snd_strerror(err));

101 exit(1);

102 }

103 /* 設(shè)置4096幀傳遞一次數(shù)據(jù) */

104 if ((err = snd_pcm_sw_params_set_avail_min(playback_handle, sw_params, 4096))

105 《 0)

106 {

107 fprintf(stderr, “cannot set minimum available count (%s)/n”, snd_strerror

108 (err));

109 exit(1);

110 }

111 /* 一旦有數(shù)據(jù)就開(kāi)始播放 */

112 if ((err = snd_pcm_sw_params_set_start_threshold(playback_handle, sw_params,

113 0U)) 《 0)

114 {

115 fprintf(stderr, “cannot set start mode (%s)/n”, snd_strerror(err));

116 exit(1);

117 }

118 if ((err = snd_pcm_sw_params(playback_handle, sw_params)) 《 0)

119 {

120 fprintf(stderr, “cannot set software parameters (%s)/n”, snd_strerror(err));

121 exit(1);

122 }

123

124 /* 每4096幀接口將中斷內(nèi)核,ALSA將很快喚醒本程序 */

125

126 if ((err = snd_pcm_prepare(playback_handle)) 《 0)

127 {

128 fprintf(stderr, “cannot prepare audio interface for use (%s)/n”,

129 snd_strerror(err));

130 exit(1);

131 }

132

133 while (1)

134 {

135

136 /* 等待,直到接口準(zhǔn)備好傳遞數(shù)據(jù),或者1秒超時(shí)發(fā)生 */

137 if ((err = snd_pcm_wait(playback_handle, 1000)) 《 0)

138 {

139 fprintf(stderr, “poll failed (%s)/n”, strerror(errno));

140 break;

141 }

142

143 /* 查出有多少空間可放置playback數(shù)據(jù) */

144 if ((frames_to_deliver = snd_pcm_avail_update(playback_handle)) 《 0)

145 {

146 if (frames_to_deliver == - EPIPE)

147 {

148 fprintf(stderr, “an xrun occured/n”);

149 break;

150 }

151 else

152 {

153 fprintf(stderr, “unknown ALSA avail update return value (%d)/n”,

154 frames_to_deliver);

155 break;

156 }

157 }

158

159 frames_to_deliver = frames_to_deliver 》 4096 ? 4096 : frames_to_deliver;

160

161 /* 傳遞數(shù)據(jù) */

162 if (playback_callback(frames_to_deliver) != frames_to_deliver)

163 {

164 fprintf(stderr, “playback callback failed/n”);

165 break;

166 }

167 }

168

169 snd_pcm_close(playback_handle);

170 exit(0);

171 }

17.5實(shí)例1:S3C2410+UDA1341 OSS驅(qū)動(dòng)

17.5.1 S3C2410與UDA1341接口硬件描述

如圖17.7,S3C2410處理器內(nèi)置了IIS總線接口,S3C2410的IIS總線時(shí)鐘信號(hào)SCK與Philip公司的UDA1341的BCK連接,字段選擇連接于WS引腳。UDA1341提供兩個(gè)音頻通道,分別用于輸入和輸出,對(duì)應(yīng)的引腳連接:IIS總線的音頻輸出IISSDO對(duì)應(yīng)于UDA1341的音頻輸入;IIS總線的音頻輸入IISSDI對(duì)應(yīng)于UDA1341的音頻輸出。UDA1341的L3接口相當(dāng)于一個(gè)混音器控制接口,可以用來(lái)控制輸入/輸出音頻信號(hào)的音量大小、低音等。L3接口的引腳L3MODE、L3DATA、L3CLOCK分別連接到S3C2410的3個(gè)GPIO來(lái)控制。

Philips 公司的UDA1341支持IIS總線數(shù)據(jù)格式,采用位元流轉(zhuǎn)換技術(shù)進(jìn)行信號(hào)處理,完成聲音信號(hào)的模數(shù)轉(zhuǎn)換,具有可編程增益放大器和數(shù)字自動(dòng)增益控制器,其低功耗、低電壓的特點(diǎn)使其非常適合用于MD/CD、筆記本電腦等便攜式設(shè)備。UDA1341對(duì)外提供2組音頻信號(hào)輸入接口,每組包括左右2個(gè)聲道。

圖17.8 UDA1341 內(nèi)部結(jié)構(gòu)

如圖17.8所示,2組音頻輸入在UDA1341內(nèi)部的處理存在很大差別:第一組音頻信號(hào)輸入后經(jīng)過(guò)1個(gè)0 dB/6 dB開(kāi)關(guān)后采樣送入數(shù)字混音器:第二組音頻信號(hào)輸入后先經(jīng)過(guò)可編程增益放大器(PGA),然后再進(jìn)行采樣,采樣后的數(shù)據(jù)要再經(jīng)過(guò)數(shù)字自動(dòng)增益控制器(AGC)送入數(shù)字混音器。設(shè)計(jì)硬件電路時(shí)選用第二組輸入音頻信號(hào),這樣可以通過(guò)軟件的方法實(shí)現(xiàn)對(duì)系統(tǒng)輸入音量大小的調(diào)節(jié)。顯然選用第二組可以通過(guò)L3總線接口控制AGC來(lái)實(shí)現(xiàn)。另外,選擇通道2還可以通過(guò)PGA對(duì)從MIC輸入的信號(hào)進(jìn)行片內(nèi)放大。

S3C2410與UDA1341之間的IIS接口有3種工作方式:

• 正常傳輸模式。該模式下使用IISCON寄存器對(duì)FIFO進(jìn)行控制,CPU通過(guò)輪詢方式訪問(wèn)FIFO寄存器,以完成對(duì)FIFO緩存?zhèn)鬏敾蚪邮盏奶幚怼?/p>

• DMA模式。通過(guò)設(shè)置IISFCON寄存器使IIS接口工作于這種模式。在該模式下,F(xiàn)IFO寄存器組的控制權(quán)掌握在DMA控制器上,當(dāng)FIFO滿時(shí),由DMA控制器對(duì)FIFO中的數(shù)據(jù)進(jìn)行處理。DMA模式的選擇由IISCON寄存器的第4和第5位控制。

• 傳輸/接收模式。該模式下,IIS數(shù)據(jù)線將通過(guò)雙通道DMA同時(shí)接收和發(fā)送音頻數(shù)據(jù)。在OSS驅(qū)動(dòng)中,將使用此模式。

17.5.2注冊(cè)dsp和mixer接口

如代碼清單17.28,在UDA1341 OSS驅(qū)動(dòng)的模塊加載函數(shù)中,將完成如下工作:

• 初始化IIS接口硬件,設(shè)置L3總線對(duì)應(yīng)的GPIO。

• 申請(qǐng)用于音頻數(shù)據(jù)傳輸?shù)腄MA通道。

• 初始化UDA1341到恰當(dāng)?shù)墓ぷ髂J健?/p>

• 注冊(cè)dsp和mixer接口。

代碼清單17.28 UDA1341 OSS驅(qū)動(dòng)模塊加載函數(shù)

1 //音頻(dsp)文件操作

2 static struct file_operations smdk2410_audio_fops =

3 {

4 llseek: smdk2410_audio_llseek,

5 write: smdk2410_audio_write,

6 read: smdk2410_audio_read,

7 poll: smdk2410_audio_poll,

8 ioctl: smdk2410_audio_ioctl,

9 open: smdk2410_audio_open,

10 release: smdk2410_audio_release

11 };

12 //混音器文件操作

13 static struct file_operations smdk2410_mixer_fops =

14 {

15 ioctl: smdk2410_mixer_ioctl,

16 open: smdk2410_mixer_open,

17 release: smdk2410_mixer_release

18 };

19

20 int __init s3c2410_uda1341_init(void)

21 {

22 unsigned long flags;

23

24 local_irq_save(flags);

25

26 /* 設(shè)置IIS接口引腳GPIO */

27

28 set_gpio_ctrl(GPIO_L3CLOCK); // GPB 4: L3CLOCK, 輸出

29 set_gpio_ctrl(GPIO_L3DATA); // GPB 3: L3DATA, 輸出

30 set_gpio_ctrl(GPIO_L3MODE); // GPB 2: L3MODE, 輸出

31

32

33 set_gpio_ctrl(GPIO_E3 | GPIO_PULLUP_EN | GPIO_MODE_IISSDI); //GPE 3: IISSDI

34 set_gpio_ctrl(GPIO_E0 | GPIO_PULLUP_EN | GPIO_MODE_IISSDI); //GPE 0: IISLRCK

35 set_gpio_ctrl(GPIO_E1 | GPIO_PULLUP_EN | GPIO_MODE_IISSCLK); //GPE 1:IISSCLK

36 set_gpio_ctrl(GPIO_E2 | GPIO_PULLUP_EN | GPIO_MODE_CDCLK); //GPE 2: CDCLK

37 set_gpio_ctrl(GPIO_E4 | GPIO_PULLUP_EN | GPIO_MODE_IISSDO); //GPE 4: IISSDO

38

39 local_irq_restore(flags);

40

41 init_uda1341();

42

43 /* 輸出流采樣DMA通道2 */

44 output_stream.dma_ch = DMA_CH2;

45

46 if (audio_init_dma(&output_stream, “UDA1341 out”))

47 {

48 audio_clear_dma(&output_stream);

49 printk(KERN_WARNING AUDIO_NAME_VERBOSE “: unable to get DMA channels/n”);

50 return - EBUSY;

51 }

52 /* 輸入流采樣DMA通道1 */

53 input_stream.dma_ch = DMA_CH1;

54

55 if (audio_init_dma(&input_stream, “UDA1341 in”))

56 {

57 audio_clear_dma(&input_stream);

58 printk(KERN_WARNING AUDIO_NAME_VERBOSE “: unable to get DMA channels/n”);

59 return - EBUSY;

60 }

61

62 /* 注冊(cè)dsp和mixer設(shè)備接口 */

63 audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, - 1);

64 audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, - 1);

65

66 printk(AUDIO_NAME_VERBOSE “ initialized/n”);

67

68 return 0;

69 }

UDA1341 OSS驅(qū)動(dòng)的模塊卸載函數(shù)中,將完成與模塊加載函數(shù)相反的工作,如代碼清單17.29。

代碼清單17.29 UDA1341 OSS驅(qū)動(dòng)模塊卸載函數(shù)

1 void __exit s3c2410_uda1341_exit(void)

2 {

3 //注銷dsp和mixer設(shè)備接口

4 unregister_sound_dsp(audio_dev_dsp);

5 unregister_sound_mixer(audio_dev_mixer);

6

7 //注銷DMA通道

8 audio_clear_dma(&output_stream);

9 audio_clear_dma(&input_stream); /* input */

10 printk(AUDIO_NAME_VERBOSE “ unloaded/n”);

11 }

17.5.3 mixer接口IO控制函數(shù)

UDA1341 OSS驅(qū)動(dòng)的ioctl()函數(shù)處理多個(gè)mixer命令,如SOUND_MIXER_INFO、 SOUND_MIXER_READ_STEREODEVS、SOUND_MIXER_WRITE_VOLUME等,用于獲得或設(shè)置音量和增益等信息,如代碼清單17.30所示。

代碼清單17.30 UDA1341 OSS驅(qū)動(dòng)ioctl()函數(shù)

1 static int smdk2410_mixer_ioctl(struct inode *inode, struct file *file,

2 unsigned int cmd, unsigned long arg)

3 {

4 int ret;

5 long val = 0;

6

7 switch (cmd)

8 {

9 case SOUND_MIXER_INFO: //獲得mixer信息

10 {

11 mixer_info info;

12 strncpy(info.id, “UDA1341”, sizeof(info.id));

13 strncpy(info.name, “Philips UDA1341”, sizeof(info.name));

14 info.modify_counter = audio_mix_modcnt;

15 return copy_to_user((void*)arg, &info, sizeof(info));

16 }

17

18 case SOUND_OLD_MIXER_INFO:

19 {

20 _old_mixer_info info;

21 strncpy(info.id, “UDA1341”, sizeof(info.id));

22 strncpy(info.name, “Philips UDA1341”, sizeof(info.name));

23 return copy_to_user((void*)arg, &info, sizeof(info));

24 }

25

26 case SOUND_MIXER_READ_STEREODEVS://獲取設(shè)備對(duì)立體聲的支持

27 return put_user(0, (long*)arg);

28

29 case SOUND_MIXER_READ_CAPS: //獲取聲卡能力

30 val = SOUND_CAP_EXCL_INPUT;

31 return put_user(val, (long*)arg);

32

33 case SOUND_MIXER_WRITE_VOLUME: //設(shè)置音量

34 ret = get_user(val, (long*)arg);

35 if (ret)

36 return ret;

37 uda1341_volume = 63-(((val &0xff) + 1) *63) / 100;

38 uda1341_l3_address(UDA1341_REG_DATA0);

39 uda1341_l3_data(uda1341_volume);

40 break;

41

42 case SOUND_MIXER_READ_VOLUME: //獲取音量

43 val = ((63-uda1341_volume) *100) / 63;

44 val |= val 《《 8;

45 return put_user(val, (long*)arg);

46

47 case SOUND_MIXER_READ_IGAIN: //獲得增益

48 val = ((31-mixer_igain) *100) / 31;

49 return put_user(val, (int*)arg);

50

51 case SOUND_MIXER_WRITE_IGAIN: //設(shè)置增益

52 ret = get_user(val, (int*)arg);

53 if (ret)

54 return ret;

55 mixer_igain = 31-(val *31 / 100);

56 /* 使用mixer增益通道1 */

57 uda1341_l3_address(UDA1341_REG_DATA0);

58 uda1341_l3_data(EXTADDR(EXT0));

59 uda1341_l3_data(EXTDATA(EXT0_CH1_GAIN(mixer_igain)));

60 break;

61

62 default:

63 DPRINTK(“mixer ioctl %u unknown/n”, cmd);

64 return - ENOSYS;

65 }

66

67 audio_mix_modcnt++;

68 return 0;

69 }

17.5.4 dsp接口音頻數(shù)據(jù)傳輸

OSS聲卡驅(qū)動(dòng)中,dsp接口的讀寫(xiě)函數(shù)是核心中的核心,直接對(duì)應(yīng)著錄音和放音的流程。

OSS 的讀函數(shù)存在一個(gè)與普通字符設(shè)備驅(qū)動(dòng)讀函數(shù)不同的地方,那就是一般而言,對(duì)于普通字符設(shè)備驅(qū)動(dòng),如果用戶要求讀count個(gè)字節(jié),而實(shí)際上只有 count1字節(jié)可獲得(count1《 count)時(shí),它會(huì)將這count1字節(jié)拷貝給用戶后即返回count1;而dsp接口的讀函數(shù)會(huì)分次拷貝,如果第1次不能滿足,它會(huì)等待第2次,直到 “count1 + count2 + 。。。 = count”為止再返回count。這種設(shè)計(jì)是合理的,因?yàn)镺SS驅(qū)動(dòng)應(yīng)該負(fù)責(zé)音頻數(shù)據(jù)的流量控制。代碼清單17.31給出了UDA1341 OSS驅(qū)動(dòng)的讀函數(shù)實(shí)現(xiàn)。

代碼清單17.31 UDA1341 OSS驅(qū)動(dòng)的讀函數(shù)

1 static ssize_t smdk2410_audio_read(struct file *file, char *buffer, size_t

2 count, loff_t *ppos)

3 {

4 const char *buffer0 = buffer;

5 audio_stream_t *s = &input_stream; //得到數(shù)據(jù)區(qū)的指針

6 int chunksize, ret = 0;

7

8 DPRINTK(“audio_read: count=%d/n”, count);

9

10 if (ppos != &file-》f_pos)

11 return - ESPIPE;

12

13 if (!s-》buffers)

14 {

15 int i;

16

17 if (audio_setup_buf(s))

18 return - ENOMEM;

19 //依次從緩存區(qū)讀取數(shù)據(jù)

20 for (i = 0; i 《 s-》nbfrags; i++)

21 {

22 audio_buf_t *b = s-》buf;

23 down(&b-》sem);

24 s3c2410_dma_queue_buffer(s-》dma_ch, (void*)b, b-》dma_addr, s-》fragsize,

25 DMA_BUF_RD);

26 NEXT_BUF(s, buf);

27 }

28 }

29

30 //滿足用戶的所有讀需求

31 while (count 》 0)

32 {

33 audio_buf_t *b = s-》buf;

34

35 if (file-》f_flags &O_NONBLOCK) //非阻塞

36 {

37 ret = - EAGAIN;

38 if (down_trylock(&b-》sem))

39 break;

40 }

41 else

42 {

43 ret = - ERESTARTSYS;

44 if (down_interruptible(&b-》sem))

45 break;

46 }

47

48 chunksize = b-》size;

49 //從緩存區(qū)讀取數(shù)據(jù)

50 if (chunksize 》 count)

51 chunksize = count;

52 DPRINTK(“read %d from %d/n”, chunksize, s-》buf_idx);

53 if (copy_to_user(buffer, b-》start + s-》fragsize - b-》size, //調(diào)用拷貝函數(shù)

54 chunksize))

55 {

56 up(&b-》sem);

57 return - EFAULT;

58 }

59 b-》size -= chunksize;

60

61 buffer += chunksize;

62 count -= chunksize; //已經(jīng)給用戶拷貝了一部分,count減少

63 if (b-》size 》 0)

64 {

65 up(&b-》sem);

66 break;

67 }

68 //將緩存區(qū)釋放

69 s3c2410_dma_queue_buffer(s-》dma_ch, (void*)b, b-》dma_addr, s-》fragsize,

70 DMA_BUF_RD);

71

72 NEXT_BUF(s, buf);

73 }

74

75 if ((buffer - buffer0))

76 ret = buffer - buffer0;

77

78 return ret;

79 }

OSS驅(qū)動(dòng)dsp接口的寫(xiě)函數(shù)與讀函數(shù)類似,一般來(lái)說(shuō),它也應(yīng)該滿足用戶的所有寫(xiě)需求后再返回,如代碼清單17.32。

代碼清單17.32 UDA1341 OSS驅(qū)動(dòng)的寫(xiě)函數(shù)

1 static ssize_t smdk2410_audio_write(struct file *file, const char *buffer,

2 size_t count, loff_t *ppos)

3 {

4 const char *buffer0 = buffer;

5 audio_stream_t *s = &output_stream;

6 int chunksize, ret = 0;

7

8 DPRINTK(“audio_write : start count=%d/n”, count);

9

10 switch (file-》f_flags &O_ACCMODE)

11 {

12 case O_WRONLY: //只寫(xiě)

13 case O_RDWR: //讀寫(xiě)

14 break;

15 default: //只讀不合法

16 return - EPERM;

17 }

18 //設(shè)置DMA緩沖區(qū)

19 if (!s-》buffers && audio_setup_buf(s))

20 return - ENOMEM;

21

22 count &= ~0x03;

23

24 while (count 》 0) //直到滿足用戶的所有寫(xiě)需求

25 {

26 audio_buf_t *b = s-》buf;

27 //非阻塞訪問(wèn)

28 if (file-》f_flags &O_NONBLOCK)

29 {

30 ret = - EAGAIN;

31 if (down_trylock(&b-》sem))

32 break;

33 }

34 else

35 {

36 ret = - ERESTARTSYS;

37 if (down_interruptible(&b-》sem))

38 break;

39 }

40 //從用戶空間拷貝音頻數(shù)據(jù)

41 if (audio_channels == 2)

42 {

43 chunksize = s-》fragsize - b-》size;

44 if (chunksize 》 count)

45 chunksize = count;

46 DPRINTK(“write %d to %d/n”, chunksize, s-》buf_idx);

47 if (copy_from_user(b-》start + b-》size, buffer, chunksize))

48 {

49 up(&b-》sem);

50 return - EFAULT;

51 }

52 b-》size += chunksize;

53 }

54 else

55 {

56 chunksize = (s-》fragsize - b-》size) 》》 1;

57

58 if (chunksize 》 count)

59 chunksize = count;

60 DPRINTK(“write %d to %d/n”, chunksize *2, s-》buf_idx);

61 if (copy_from_user_mono_stereo(b-》start + b-》size, buffer, chunksize))

62 {

63 up(&b-》sem);

64 return - EFAULT;

65 }

66

67 b-》size += chunksize * 2;

68 }

69

70 buffer += chunksize;

71 count -= chunksize; //已經(jīng)從用戶拷貝了一部分,count減少

72 if (b-》size 《 s-》fragsize)

73 {

74 up(&b-》sem);

75 break;

76 }

77 //發(fā)起DMA操作

78 s3c2410_dma_queue_buffer(s-》dma_ch, (void*)b, b-》dma_addr, b-》size,

79 DMA_BUF_WR);

80 b-》size = 0;

81 NEXT_BUF(s, buf);

82 }

83

84 if ((buffer - buffer0))

85 ret = buffer - buffer0;

86

87 DPRINTK(“audio_write : end count=%d/n/n”, ret);

88

89 return ret;

90 }

17.6實(shí)例2:SA1100+ UDA1341 ALSA驅(qū)動(dòng)

17.6.1 card注冊(cè)與注銷

同樣是UDA1341芯片,如果以ALSA體系結(jié)構(gòu)來(lái)實(shí)現(xiàn)它的驅(qū)動(dòng),會(huì)和OSS大不一樣。如17.4.1節(jié)所言,在模塊初始化和卸載的時(shí)候,需要注冊(cè)和注銷card,另外在模塊加載的時(shí)候,也會(huì)注冊(cè)mixer和pcm組件,如代碼清單17.33。

代碼清單17.33 UDA1341 ALSA驅(qū)動(dòng)模塊初始化與卸載

1 static int __init sa11xx_uda1341_probe(struct platform_device *devptr)

2 {

3 int err;

4 struct snd_card *card;

5 struct sa11xx_uda1341 *chip;

6

7 /* 新建card */

8 card = snd_card_new(-1, id, THIS_MODULE, sizeof(struct sa11xx_uda1341));

9 if (card == NULL)

10 return -ENOMEM;

11

12 chip = card-》private_data;

13 spin_lock_init(&chip-》s[0].dma_lock);

14 spin_lock_init(&chip-》s[1].dma_lock);

15

16 card-》private_free = snd_sa11xx_uda1341_free;//card私有數(shù)據(jù)釋放

17 chip-》card = card;

18 chip-》samplerate = AUDIO_RATE_DEFAULT;

19

20 // 注冊(cè)control(mixer)接口

21 if ((err = snd_chip_uda1341_mixer_new(card, &chip-》uda1341)))

22 goto nodev;

23

24 // 注冊(cè)PCM接口

25 if ((err = snd_card_sa11xx_uda1341_pcm(chip, 0)) 《 0)

26 goto nodev;

27

28 strcpy(card-》driver, “UDA1341”);

29 strcpy(card-》shortname, “H3600 UDA1341TS”);

30 sprintf(card-》longname, “Compaq iPAQ H3600 with Philips UDA1341TS”);

31

32 snd_card_set_dev(card, &devptr-》dev);

33 //注冊(cè)card

34 if ((err = snd_card_register(card)) == 0) {

35 printk( KERN_INFO “iPAQ audio support initialized/n” );

36 platform_set_drvdata(devptr, card);

37 return 0;

38 }

39

40 nodev:

41 snd_card_free(card);

42 return err;

43 }

44

45 static int __devexit sa11xx_uda1341_remove(struct platform_device *devptr)

46 {

47 //釋放card

48 snd_card_free(platform_get_drvdata(devptr));

49 platform_set_drvdata(devptr, NULL);

50 return 0;

51 }

17.6.2 PCM設(shè)備的實(shí)現(xiàn)

PCM組件直接對(duì)應(yīng)著ALSA驅(qū)動(dòng)的錄音和放音,從17.4.2節(jié)的描述可知,驅(qū)動(dòng)從需要定義對(duì)應(yīng)相應(yīng)的snd_pcm_hardware結(jié)構(gòu)體進(jìn)行PCM設(shè)備硬件描述,如代碼清單17.34。

代碼清單17.34 UDA1341 ALSA驅(qū)動(dòng)PCM接口snd_pcm_hardware結(jié)構(gòu)體

1 static struct snd_pcm_hardware snd_sa11xx_uda1341_capture =

2 {

3 .info = (SNDRV_PCM_INFO_INTERLEAVED |

4 SNDRV_PCM_INFO_BLOCK_TRANSFER |

5 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |

6 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),

7 .formats = SNDRV_PCM_FMTBIT_S16_LE,

8 .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |/

9 SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |/

10 SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |/

11 SNDRV_PCM_RATE_KNOT),

12 .rate_min = 8000,

13 .rate_max = 48000,

14 .channels_min = 2,

15 .channels_max = 2,

16 .buffer_bytes_max = 64*1024,

17 .period_bytes_min = 64,

18 .period_bytes_max = DMA_BUF_SIZE,

19 .periods_min = 2,

20 .periods_max = 255,

21 .fifo_size = 0,

22 };

23

24 static struct snd_pcm_hardware snd_sa11xx_uda1341_playback =

25 {

26 .info = (SNDRV_PCM_INFO_INTERLEAVED |

27 SNDRV_PCM_INFO_BLOCK_TRANSFER |

28 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |

29 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),

30 .formats = SNDRV_PCM_FMTBIT_S16_LE,

31 .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |/

32 SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |/

33 SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |/

34 SNDRV_PCM_RATE_KNOT),

35 .rate_min = 8000,

36 .rate_max = 48000,

37 .channels_min = 2,

38 .channels_max = 2,

39 .buffer_bytes_max = 64*1024,

40 .period_bytes_min = 64,

41 .period_bytes_max = DMA_BUF_SIZE,

42 .periods_min = 2,

43 .periods_max = 255,

44 .fifo_size = 0,

45 };

PCM接口的主要函數(shù)被封裝在snd_pcm_ops結(jié)構(gòu)體內(nèi),UDA1341 ALSA驅(qū)動(dòng)對(duì)snd_pcm_ops結(jié)構(gòu)體的定義如代碼清單17.35。

代碼清單17.35 UDA1341 ALSA驅(qū)動(dòng)PCM接口snd_pcm_ops結(jié)構(gòu)體

1 static struct snd_pcm_ops snd_card_sa11xx_uda1341_playback_ops =

2 {

3 .open = snd_card_sa11xx_uda1341_open,

4 .close = snd_card_sa11xx_uda1341_close,

5 .ioctl = snd_pcm_lib_ioctl,

6 .hw_params = snd_sa11xx_uda1341_hw_params,

7 .hw_free = snd_sa11xx_uda1341_hw_free,

8 .prepare = snd_sa11xx_uda1341_prepare,

9 .trigger = snd_sa11xx_uda1341_trigger,

10 .pointer = snd_sa11xx_uda1341_pointer,

11 };

12

13 static struct snd_pcm_ops snd_card_sa11xx_uda1341_capture_ops =

14 {

15 .open = snd_card_sa11xx_uda1341_open,

16 .close = snd_card_sa11xx_uda1341_close,

17 .ioctl = snd_pcm_lib_ioctl,

18 .hw_params = snd_sa11xx_uda1341_hw_params,

19 .hw_free = snd_sa11xx_uda1341_hw_free,

20 .prepare = snd_sa11xx_uda1341_prepare,

21 .trigger = snd_sa11xx_uda1341_trigger,

22 .pointer = snd_sa11xx_uda1341_pointer,

23 };

代碼清單17.33第25行調(diào)用的snd_card_sa11xx_uda1341_pcm()即是PCM組件的“構(gòu)造函數(shù)”,其實(shí)現(xiàn)如代碼清單17.36。

代碼清單17.36 UDA1341 ALSA驅(qū)動(dòng)PCM組件構(gòu)造函數(shù)

1 static int __init snd_card_sa11xx_uda1341_pcm(struct sa11xx_uda1341 *sa11xx_uda1341, int device)

2 {

3 struct snd_pcm *pcm;

4 int err;

5 /* 新建pcm設(shè)備,playback和capture都為1個(gè) */

6 if ((err = snd_pcm_new(sa11xx_uda1341-》card, “UDA1341 PCM”, device, 1, 1, &pcm)) 《 0)

7 return err;

8

9 /* 建立初始緩沖區(qū)并設(shè)置dma_type為isa */

10 snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,

11 snd_dma_isa_data(),

12 64*1024, 64*1024);

13 /* 設(shè)置pcm的操作 */

14 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, snd_card_sa11xx_uda1341_playback_ops);

15 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_card_sa11xx_uda1341_capture_ops);

16 pcm-》private_data = sa11xx_uda1341;

17 pcm-》info_flags = 0;

18 strcpy(pcm-》name, “UDA1341 PCM”);

19

20 sa11xx_uda1341_audio_init(sa11xx_uda1341);

21

22 /* 設(shè)置DMA控制器 */

23 audio_dma_request(&sa11xx_uda1341-》s[SNDRV_PCM_STREAM_PLAYBACK], audio_dma_callback);

24 audio_dma_request(&sa11xx_uda1341-》s[SNDRV_PCM_STREAM_CAPTURE], audio_dma_callback);

25

26 sa11xx_uda1341-》pcm = pcm;

27

28 return 0;

29 }

在snd_pcm_ops結(jié)構(gòu)體的打開(kāi)成員函數(shù)中,需要根據(jù)具體的子流賦值snd_pcm_runtime的hw,如代碼清單17.37。

代碼清單17.37 UDA1341 ALSA驅(qū)動(dòng)snd_pcm_ops結(jié)構(gòu)體open/close成員函數(shù)

1 static int snd_card_sa11xx_uda1341_open(struct snd_pcm_substream *substream)

2 {

3 struct sa11xx_uda1341 *chip = snd_pcm_substream_chip(substream);

4 struct snd_pcm_runtime *runtime = substream-》runtime;

5 int stream_id = substream-》pstr-》stream;

6 int err;

7

8 chip-》s[stream_id].stream = substream;

9

10 if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) //播放子流

11 runtime-》hw = snd_sa11xx_uda1341_playback;

12 else //錄音子流

13 runtime-》hw = snd_sa11xx_uda1341_capture;

14 if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) 《 0)

15 return err;

16 if ((err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,&hw_constraints_rates)) 《 0)

17 return err;

18

19 return 0;

20 }

21

22 static int snd_card_sa11xx_uda1341_close(struct snd_pcm_substream *substream)

23 {

24 struct sa11xx_uda1341 *chip = snd_pcm_substream_chip(substream);

25

26 chip-》s[substream-》pstr-》stream].stream = NULL;

27 return 0;

28 }

在snd_pcm_ops結(jié)構(gòu)體的trigger()成員函數(shù)中,控制播放和錄音的啟/停、掛起/恢復(fù),如代碼清單17.38。

代碼清單17.38 UDA1341 ALSA驅(qū)動(dòng)snd_pcm_ops結(jié)構(gòu)體trigger成員函數(shù)

1 static int snd_sa11xx_uda1341_trigger(struct snd_pcm_substream *substream, int

2 cmd)

3 {

4 struct sa11xx_uda1341 *chip = snd_pcm_substream_chip(substream);

5 int stream_id = substream-》pstr-》stream;

6 struct audio_stream *s = &chip-》s[stream_id];

7 struct audio_stream *s1 = &chip-》s[stream_id ^ 1];

8 int err = 0;

9

10 /* 注意本地中斷已經(jīng)被中間層代碼禁止 */

11 spin_lock(&s-》dma_lock);

12 switch (cmd)

13 {

14 case SNDRV_PCM_TRIGGER_START://開(kāi)啟PCM

15 if (stream_id == SNDRV_PCM_STREAM_CAPTURE && !s1-》active) //開(kāi)啟錄音,不在播放

16 {

17 s1-》tx_spin = 1;

18 audio_process_dma(s1);

19 }

20 else

21 {

22 s-》tx_spin = 0;

23 }

24

25 /* 被請(qǐng)求的流啟動(dòng) */

26 s-》active = 1;

27 audio_process_dma(s);

28 break;

29 case SNDRV_PCM_TRIGGER_STOP:

30 /* 被請(qǐng)求的流關(guān)閉 */

31 audio_stop_dma(s);

32 if (stream_id == SNDRV_PCM_STREAM_PLAYBACK && s1-》active) //在錄音時(shí)開(kāi)啟播放

33 {

34 s-》tx_spin = 1;

35 audio_process_dma(s); //啟動(dòng)DMA

36 }

37 else

38 {

39 if (s1-》tx_spin)

40 {

41 s1-》tx_spin = 0;

42 audio_stop_dma(s1); //停止DMA

43 }

44 }

45

46 break;

47 case SNDRV_PCM_TRIGGER_SUSPEND: //掛起

48 s-》active = 0;

49 #ifdef HH_VERSION

50 sa1100_dma_stop(s-》dmach); //停止DMA

51 #endif

52 s-》old_offset = audio_get_dma_pos(s) + 1;

53 #ifdef HH_VERSION

54 sa1100_dma_flush_all(s-》dmach);

55 #endif

56 s-》periods = 0;

57 break;

58 case SNDRV_PCM_TRIGGER_RESUME: //恢復(fù)

59 s-》active = 1;

60 s-》tx_spin = 0;

61 audio_process_dma(s); //開(kāi)啟DMA

62 if (stream_id == SNDRV_PCM_STREAM_CAPTURE && !s1-》active)

63 {

64 s1-》tx_spin = 1;

65 audio_process_dma(s1);

66 }

67 break;

68 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: //暫停

69 #ifdef HH_VERSION

70 sa1100_dma_stop(s-》dmach); //停止DMA

71 #endif

72 s-》active = 0;

73 if (stream_id == SNDRV_PCM_STREAM_PLAYBACK)

74 {

75 if (s1-》active)

76 {

77 s-》tx_spin = 1;

78 s-》old_offset = audio_get_dma_pos(s) + 1;

79 #ifdef HH_VERSION

80 sa1100_dma_flush_all(s-》dmach);

81 #endif

82 audio_process_dma(s); //開(kāi)啟DMA

83 }

84 }

85 else

86 {

87 if (s1-》tx_spin)

88 {

89 s1-》tx_spin = 0;

90 #ifdef HH_VERSION

91 sa1100_dma_flush_all(s1-》dmach);

92 #endif

93 }

94 }

95 break;

96 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: //暫停釋放

97 s-》active = 1;

98 if (s-》old_offset)

99 {

100 s-》tx_spin = 0;

101 audio_process_dma(s);

102 break;

103 }

104 if (stream_id == SNDRV_PCM_STREAM_CAPTURE && !s1-》active)

105 {

106 s1-》tx_spin = 1;

107 audio_process_dma(s1);

108 }

109 #ifdef HH_VERSION

110 sa1100_dma_resume(s-》dmach);

111 #endif

112 break;

113 default:

114 err = - EINVAL;

115 break;

116 }

117 spin_unlock(&s-》dma_lock);

118 return err;

119 }

snd_pcm_ops結(jié)構(gòu)體中其它的hw_params()、prepare()、pointer()等成員函數(shù)實(shí)現(xiàn)較為簡(jiǎn)單,這里不再贅述。

17.6.3 控制接口的實(shí)現(xiàn)

代碼清單17.33第21行調(diào)用的snd_chip_uda1341_mixer_new()可以認(rèn)為是UDA1341 ALSA驅(qū)動(dòng)mixer控制組件的“構(gòu)造函數(shù)”,其中會(huì)創(chuàng)建的控制元素的定義如代碼清單17.39,包括一些枚舉和單值元素。

代碼清單17.39 UDA1341 ALSA驅(qū)動(dòng)控制接口snd_kcontrol_new結(jié)構(gòu)體

1 #define UDA1341_SINGLE(xname, where, reg, shift, mask, invert) /

2 { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_uda1341_info_single, /

3 .get = snd_uda1341_get_single, .put = snd_uda1341_put_single, /

4 .private_value = where | (reg 《《 5) | (shift 《《 9) | (mask 《《 12) | (invert 《《 18) /

5 }

6

7 #define UDA1341_ENUM(xname, where, reg, shift, mask, invert) /

8 { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_uda1341_info_enum, /

9 .get = snd_uda1341_get_enum, .put = snd_uda1341_put_enum, /

10 .private_value = where | (reg 《《 5) | (shift 《《 9) | (mask 《《 12) | (invert 《《 18) /

11 }

12

13 static struct snd_kcontrol_new snd_uda1341_controls[] =

14 {

15 UDA1341_SINGLE(“Master Playback Switch”, CMD_MUTE, data0_2, 2, 1, 1),

16 UDA1341_SINGLE(“Master Playback Volume”, CMD_VOLUME, data0_0, 0, 63, 1),

17

18 UDA1341_SINGLE(“Bass Playback Volume”, CMD_BASS, data0_1, 2, 15, 0),

19 UDA1341_SINGLE(“Treble Playback Volume”, CMD_TREBBLE, data0_1, 0, 3, 0),

20

21 UDA1341_SINGLE(“Input Gain Switch”, CMD_IGAIN, stat1, 5, 1, 0),

22 UDA1341_SINGLE(“Output Gain Switch”, CMD_OGAIN, stat1, 6, 1, 0),

23

24 UDA1341_SINGLE(“Mixer Gain Channel 1 Volume”, CMD_CH1, ext0, 0, 31, 1),

25 UDA1341_SINGLE(“Mixer Gain Channel 2 Volume”, CMD_CH2, ext1, 0, 31, 1),

26

27 UDA1341_SINGLE(“Mic Sensitivity Volume”, CMD_MIC, ext2, 2, 7, 0),

28

29 UDA1341_SINGLE(“AGC Output Level”, CMD_AGC_LEVEL, ext6, 0, 3, 0),

30 UDA1341_SINGLE(“AGC Time Constant”, CMD_AGC_TIME, ext6, 2, 7, 0),

31 UDA1341_SINGLE(“AGC Time Constant Switch”, CMD_AGC, ext4, 4, 1, 0),

32

33 UDA1341_SINGLE(“DAC Power”, CMD_DAC, stat1, 0, 1, 0),

34 UDA1341_SINGLE(“ADC Power”, CMD_ADC, stat1, 1, 1, 0),

35

36 UDA1341_ENUM(“Peak detection”, CMD_PEAK, data0_2, 5, 1, 0),

37 UDA1341_ENUM(“De-emphasis”, CMD_DEEMP, data0_2, 3, 3, 0),

38 UDA1341_ENUM(“Mixer mode”, CMD_MIXER, ext2, 0, 3, 0),

39 UDA1341_ENUM(“Filter mode”, CMD_FILTER, data0_2, 0, 3, 0),

40

41 UDA1341_2REGS(“Gain Input Amplifier Gain (channel 2)”, CMD_IG, ext4, ext5, 0, 0, 3, 31, 0),

42 };

從上述代碼中宏UDA1341_SINGLE和UDA1341_ENUM的定義可知,單值元素的info()、get()、put()成員函數(shù)分別為 snd_uda1341_info_single()、snd_uda1341_get_single()和 snd_uda1341_put_single();枚舉元素的info()、get()、put()成員函數(shù)分別為 snd_uda1341_info_enum()、snd_uda1341_get_enum()、和snd_uda1341_put_enum()。作為例子,代碼清單17.40給出了單值元素相關(guān)函數(shù)的實(shí)現(xiàn)。

代碼清單17.40 UDA1341 ALSA驅(qū)動(dòng)控制接口單值元素info/get/put函數(shù)

1 static int snd_uda1341_info_single(struct snd_kcontrol *kcontrol, struct

2 snd_ctl_elem_info *uinfo)

3 {

4 int mask = (kcontrol-》private_value 》》 12) &63;

5

6 uinfo-》type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN :

7 SNDRV_CTL_ELEM_TYPE_INTEGER;

8 uinfo-》count = 1; //數(shù)量為1

9 uinfo-》value.integer.min = 0; //最小值

10 uinfo-》value.integer.max = mask; //最大值

11 return 0;

12 }

13

14 static int snd_uda1341_get_single(struct snd_kcontrol *kcontrol, struct

15 snd_ctl_elem_value *ucontrol)

16 {

17 struct l3_client *clnt = snd_kcontrol_chip(kcontrol);

18 struct uda1341 *uda = clnt-》driver_data;

19 int where = kcontrol-》private_value &31;

20 int mask = (kcontrol-》private_value 》》 12) &63;

21 int invert = (kcontrol-》private_value 》》 18) &1;

22

23 ucontrol-》value.integer.value[0] = uda-》cfg[where]; //返回給ucontrol

24 if (invert) //如果反轉(zhuǎn)

25 ucontrol-》value.integer.value[0] = mask - ucontrol-》value.integer.value[0];

26

27 return 0;

28 }

29

30 static int snd_uda1341_put_single(struct snd_kcontrol *kcontrol, struct

31 snd_ctl_elem_value *ucontrol)

32 {

33 struct l3_client *clnt = snd_kcontrol_chip(kcontrol);

34 struct uda1341 *uda = clnt-》driver_data;

35 int where = kcontrol-》private_value &31;

36 int reg = (kcontrol-》private_value 》》 5) &15;

37 int shift = (kcontrol-》private_value 》》 9) &7;

38 int mask = (kcontrol-》private_value 》》 12) &63;

39 int invert = (kcontrol-》private_value 》》 18) &1;

40 unsigned short val;

41

42 val = (ucontrol-》value.integer.value[0] &mask);//從ucontrol獲得值

43 if (invert) //如果反轉(zhuǎn)

44 val = mask - val;

45

46 uda-》cfg[where] = val;

47 return snd_uda1341_update_bits(clnt, reg, mask, shift, val, FLUSH);//更新位

48 }

17.7實(shí)例3:PXA255+AC97 ALSA驅(qū)動(dòng)

Intel 公司的XScale PXA255是一款基于ARM5TE內(nèi)核技術(shù)的嵌入式處理器。它提供了符合AC97 rev2.0標(biāo)準(zhǔn)的AC97控制單元(ACUNIT)和音頻控制連接(AC-Link)。ACUNIT就是CODEC控制器,它通過(guò)AC-Link連接和控制AC97 CODEC芯片,例如Philips公司出品的一款符合AC97標(biāo)準(zhǔn)的多功能CODEC芯片UCB1400。它不僅是一枚CODEC芯片,還集成了觸摸和能量管理兩個(gè)功能模塊,在嵌入式系統(tǒng)中應(yīng)用廣泛。

AC-Link 連接了ACUNIT 和UCB1400Codec,只要對(duì)ACUNIT寄存器操作就可以實(shí)現(xiàn)同UCBI400之間的數(shù)據(jù)傳輸。通過(guò)ACUNIT還可讀寫(xiě)CODEC內(nèi)部寄存器,實(shí)現(xiàn)對(duì)音頻采樣和混音處理的控制。當(dāng)然這些讀寫(xiě)操作也是經(jīng)由AC-Link傳輸?shù)摹?/p>

Intel Xscale PXA255提供了16個(gè)DMA通道,可以很方便的為外圍設(shè)備提供數(shù)據(jù)傳送。ACUNIT也為CODEC的立體聲輸入輸出和話筒輸入提供了獨(dú)立的16 bit數(shù)據(jù)通道。每個(gè)通道都有一個(gè)專門的FIFO。

由于它是一個(gè)標(biāo)準(zhǔn)的AC97設(shè)備,因此,其驅(qū)動(dòng)的控制部分實(shí)現(xiàn)可以說(shuō)是非常的簡(jiǎn)單,按照17.4.4節(jié)的要求,需要實(shí)現(xiàn)AC97 codec寄存器讀寫(xiě)的硬件級(jí)函數(shù)pxa2xx_ac97_read()和pxa2xx_ac97_write(),并在模塊初始化時(shí)注冊(cè)相關(guān)的AC97 組件,如代碼清單17.41。

代碼清單17.41 PXA255連接AC97 codec ALSA驅(qū)動(dòng)控制組件

1 static unsigned short pxa2xx_ac97_read(struct snd_ac97 *ac97, unsigned short

2 reg)

3 {

4 unsigned short val = - 1;

5 volatile u32 *reg_addr;

6

7 down(&car_mutex);

8

9 /* 設(shè)置首/次codec空間 */

10 reg_addr = (ac97-》num &1) ? &SAC_REG_BASE: &PAC_REG_BASE;

11 reg_addr += (reg 》》 1);

12

13 /* 通過(guò)ac97 link讀 */

14 GSR = GSR_CDONE | GSR_SDONE;

15 gsr_bits = 0;

16 val = *reg_addr;

17 if (reg == AC97_GPIO_STATUS)

18 goto out;

19 if (wait_event_timeout(gsr_wq, (GSR | gsr_bits) &GSR_SDONE, 1) 《= 0

20 && !((GSR | gsr_bits) &GSR_SDONE))

21 //等待

22 {

23 printk(KERN_ERR “%s: read error (ac97_reg=%d GSR=%#lx)/n”,

24 __FUNCTION__,reg, GSR | gsr_bits);

25 val = - 1;

26 goto out;

27 }

28

29 /* 置數(shù)據(jù)有效 */

30 GSR = GSR_CDONE | GSR_SDONE;

31 gsr_bits = 0;

32 val = *reg_addr;

33 /* 但是我們已經(jīng)開(kāi)啟另一個(gè)周期。。。 */

34 wait_event_timeout(gsr_wq, (GSR | gsr_bits) &GSR_SDONE, 1);

35

36 out: up(&car_mutex);

37 return val;

38 }

39

40 static void pxa2xx_ac97_write(struct snd_ac97 *ac97, unsigned short reg,

41 unsigned short val)

42 {

43 volatile u32 *reg_addr;

44

45 down(&car_mutex);

46

47 /*設(shè)置首/次codec空間*/

48 reg_addr = (ac97-》num &1) ? &SAC_REG_BASE: &PAC_REG_BASE;

49 reg_addr += (reg 》》 1);

50

51 GSR = GSR_CDONE | GSR_SDONE;

52 gsr_bits = 0;

53 *reg_addr = val; //通過(guò)ac97 link寫(xiě)

54 if (wait_event_timeout(gsr_wq, (GSR | gsr_bits) &GSR_CDONE, 1) 《= 0

55 && !((GSR | gsr_bits) &GSR_CDONE))

56 printk(KERN_ERR “%s: write error (ac97_reg=%d GSR=%#lx)/n”,

57 __FUNCTION__,reg, GSR | gsr_bits);

58

59 up(&car_mutex);

60 }

61

62 static int pxa2xx_ac97_probe(struct platform_device *dev)

63 {

64 struct snd_card *card;

65 struct snd_ac97_bus *ac97_bus;

66 struct snd_ac97_template ac97_template;

67 int ret;

68

69 ret = - ENOMEM;

70 /* 新建card */

71 card = snd_card_new(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, THIS_MODULE, 0);

72 if (!card)

73 goto err;

74 card-》dev = &dev-》dev;

75 strncpy(card-》driver, dev-》dev.driver-》name, sizeof(card-》driver));

76

77 /* 構(gòu)造pcm組件 */

78 ret = pxa2xx_pcm_new(card, &pxa2xx_ac97_pcm_client, &pxa2xx_ac97_pcm);

79 if (ret)

80 goto err;

81

82 /* 申請(qǐng)中斷 */

83 ret = request_irq(IRQ_AC97, pxa2xx_ac97_irq, 0, “AC97”, NULL);

84 if (ret 《 0)

85 goto err;

86

87 。。。

88

89 /* 初始化ac97 bus */

90 ret = snd_ac97_bus(card, 0, &pxa2xx_ac97_ops, NULL, &ac97_bus);

91 if (ret)

92 goto err;

93 memset(&ac97_template, 0, sizeof(ac97_template));

94 ret = snd_ac97_mixer(ac97_bus, &ac97_template, &pxa2xx_ac97_ac97);

95 if (ret)

96 goto err;

97 。。。

98

99 /* 注冊(cè)card */

100 ret = snd_card_register(card);

101 if (ret == 0)

102 {

103 platform_set_drvdata(dev, card);

104 return 0;

105 }

106

107 err: if (card)

108 snd_card_free(card);

109 。。。

110

111 returns ret;

112 }

17.8總結(jié)

音頻設(shè)備接口包括PCM、IIS和AC97幾種,分別適用于不同的應(yīng)用場(chǎng)合。針對(duì)音頻設(shè)備,Linux內(nèi)核中包含了2類音頻設(shè)備驅(qū)動(dòng)框架,OSS和 ALSA,前者包含dsp和mixer字符設(shè)備接口,在用戶空間的編程中,完全使用文件操作;后者以card和組件(pcm、mixer等)為主線,在用戶空間的編程中不使用文件接口而使用alsalib。

在音頻設(shè)備驅(qū)動(dòng)中,幾乎必須使用DMA,而DMA的緩沖區(qū)會(huì)被分割成一個(gè)一個(gè)的段,每次 DMA操作進(jìn)行其中的一段。OSS驅(qū)動(dòng)的阻塞讀寫(xiě)具有流控能力,在用戶空間不需要進(jìn)行流量方面的定時(shí)工作,但是它需要及時(shí)的寫(xiě)(播放)和讀(錄音),以免出現(xiàn)緩沖區(qū)的underflow或overflow。

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開(kāi)發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來(lái)越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來(lái)越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開(kāi)幕式在貴陽(yáng)舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語(yǔ)權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉