九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区
打開APP
未登錄
開通VIP,暢享免費電子書等14項超值服
開通VIP
首頁
好書
留言交流
下載APP
聯(lián)系客服
Linux 音頻設備驅(qū)動
guitarhua
>《理學》
2012.11.18
關注
Linux 音頻設備驅(qū)動
分類:
ALSA && OSS
2012-09-22 17:56
351人閱讀
評論
(0)
收藏
舉報
第十七章 Linux 音頻設備驅(qū)動
本章導讀
在Linux 中,先后出現(xiàn)了音頻設備的兩種框架OSS 和ALSA,本節(jié)將在介紹數(shù)字音頻設備及音頻設備硬件接
口的基礎上,展現(xiàn)OSS 和ALSA 驅(qū)動的結構。
17.1~17.2 節(jié)講解了音頻設備及PCM、IIS 和AC97 硬件接口。
17.3 節(jié)闡述了Linux OSS 音頻設備驅(qū)動的組成、mixer 接口、dsp 接口及用戶空間編程方法。
17.4 節(jié)闡述了Linux ALSA 音頻設備驅(qū)動的組成、card 和組件管理、PCM 設備、control 接口、AC97 API
及用戶空間編程方法。
17.5 節(jié)以S3C2410 通過IIS 接口外接UDA1341 編解碼器的實例講解了OSS 驅(qū)動。
17.6 節(jié)以PXA255 通過AC97 接口外接AC97 編解碼器的實例講解了ALSA 驅(qū)動。
17.1 數(shù)字音頻設備
目前,手機、PDA、MP3 等許多嵌入式設備中包含了數(shù)字音頻設備,一個典型的數(shù)字音頻系統(tǒng)的電路組成如
圖17.1 所示。圖17.1 中的嵌入式微控制器/DSP 中集成了PCM、IIS 或AC97 音頻接口,通過這些接口連接
外部的音頻編解碼器即可實現(xiàn)聲音的AD 和DA 轉(zhuǎn)換,圖中的功放完成模擬信號的放大功能。
圖17.1 典型的數(shù)字音頻系統(tǒng)電路
音頻編解碼器是數(shù)字音頻系統(tǒng)的核心,衡量它的指標主要有:
? 采樣頻率
采樣的過程就是將通常的模擬音頻信號的電信號轉(zhuǎn)換成二進制碼0 和1 的過程,這些0 和1 便構成了數(shù)字
音頻文件。如圖17.2 中的正弦曲線代表原始音頻曲線,方格代表采樣后得到的結果,二者越吻合說明采樣
結果越好。
采樣頻率是每秒鐘的采樣次數(shù),我們常說的 44.1kHz 采樣頻率就是每秒鐘采樣44100 次。理論上采樣頻
率越高,轉(zhuǎn)換精度越高,目前主流的采樣頻率是48kHz。
? 量化精度
量化精度是指對采樣數(shù)據(jù)分析的精度,比如24bit 量化精度就是是將標準電平信號按照2 的24 次方進行分
析,也就是說將圖17.2 中的縱坐標等分為224 等分。量化精度越高,聲音就越逼真。
圖17.2 數(shù)字音頻采樣
17.2 音頻設備硬件接口
17.2.1 PCM 接口
針對不同的數(shù)字音頻子系統(tǒng),出現(xiàn)了幾種微處理器或DSP 與音頻器件間用于數(shù)字轉(zhuǎn)換的接口。
最簡單的音頻接口是PCM(脈沖編碼調(diào)制)接口,該接口由時鐘脈沖(BCLK)、幀同步信號(FS)及接收
數(shù)據(jù)(DR)和發(fā)送數(shù)據(jù)(DX)組成。在FS 信號的上升沿,數(shù)據(jù)傳輸從MSB(Most Significant Bit)字開
始,F(xiàn)S 頻率等于采樣率。FS 信號之后開始數(shù)據(jù)字的傳輸,單個的數(shù)據(jù)位按順序進行傳輸,1 個時鐘周期傳
輸1 個數(shù)據(jù)字。發(fā)送MSB 時,信號的等級首先降到最低,以避免在不同終端的接口使用不同的數(shù)據(jù)方案時
造成MSB 的丟失。
PCM 接口很容易實現(xiàn),原則上能夠支持任何數(shù)據(jù)方案和任何采樣率,但需要每個音頻通道獲得一個獨立的
數(shù)據(jù)隊列。
17.2.2 IIS 接口
IIS 接口(Inter-IC Sound)在20 世紀80 年代首先被飛利浦用于消費音頻,并在一個稱為LRCLK(Left/Right
CLOCK)的信號機制中經(jīng)過多路轉(zhuǎn)換,將兩路音頻信號變成單一的數(shù)據(jù)隊列。當LRCLK 為高時,左聲道數(shù)據(jù)
被傳輸;LRCLK 為低時,右聲道數(shù)據(jù)被傳輸。與PCM 相比,IIS 更適合于立體聲系統(tǒng)。對于多通道系統(tǒng),在
同樣的BCLK 和LRCLK 條件下,并行執(zhí)行幾個數(shù)據(jù)隊列也是可能的。
17.2.3 AC97 接口
AC'97(Audio Codec 1997)是以Intel 為首的五個PC 廠商Intel、Creative Labs、NS、Analog Device
與Yamaha 共同提出的規(guī)格標準。與PCM 和IIS 不同,AC'97 不只是一種數(shù)據(jù)格式,用于音頻編碼的內(nèi)部架
構規(guī)格,它還具有控制功能。AC'97 采用AC-Link 與外部的編解碼器相連,AC-Link 接口包括位時鐘(BITCLK)、
同步信號校正(SYNC)和從編碼到處理器及從處理器中解碼(SDATDIN 與SDATAOUT)的數(shù)據(jù)隊列。AC'97
數(shù)據(jù)幀以SYNC 脈沖開始,包括12 個20 位時間段(時間段為標準中定義的不同的目的服務)及16 位“tag”
段,共計256 個數(shù)據(jù)序列。例如,時間段“1”和“2”用于訪問編碼的控制寄存器,而時間段“3”和“4”
分別負載左、右兩個音頻通道?!皌ag”段表示其他段中哪一個包含有效數(shù)據(jù)。把幀分成時間段使傳輸控制
信號和音頻數(shù)據(jù)僅通過4 根線到達9 個音頻通道或轉(zhuǎn)換成其他數(shù)據(jù)流成為可能。與具有分離控制接口的IIS
方案相比,AC'97 明顯減少了整體管腳數(shù)。一般來說,AC'97 編解碼器采用TQFP48 封裝,如圖17.3 所示。
圖17.3 AC97 Codec 芯片
PCM、IIS 和AC97 各有其優(yōu)點和應用范圍,例如在CD、MD、MP3 隨身聽多采用IIS 接口,移動電話會采用
PCM 接口,具有音頻功能的PDA 則多使用和PC 一樣的AC'97 編碼格式。
17.3 Linux OSS 音頻設備驅(qū)動
17.3.1 OSS 驅(qū)動的組成
OSS 標準中有2 個最基本的音頻設備:mixer(混音器)和DSP(數(shù)字信號處理器)。
在聲卡的硬件電路中,mixer 是一個很重要的組成部分,它的作用是將多個信號組合或者疊加在一起,對
于不同的聲卡來說,其混音器的作用可能各不相同。OSS 驅(qū)動中,/dev/mixer 設備文件是應用程序?qū)ixer
進行操作的軟件接口。
混音器電路通常由兩個部分組成:輸入混音器(input mixer)和輸出混音器(output mixer)。輸入混音
器負責從多個不同的信號源接收模擬信號,這些信號源有時也被稱為混音通道或者混音設備。模擬信號通
過增益控制器和由軟件控制的音量調(diào)節(jié)器后,在不同的混音通道中進行級別(level)調(diào)制,然后被送到輸
入混音器中進行聲音的合成?;煲羝魃系碾娮娱_關可以控制哪些通道中有信號與混音器相連,有些聲卡只
允許連接一個混音通道作為錄音的音源,而有些聲卡則允許對混音通道做任意的連接。經(jīng)過輸入混音器處
理后的信號仍然為模擬信號,它們將被送到A/D 轉(zhuǎn)換器進行數(shù)字化處理。
輸出混音器的工作原理與輸入混音器類似,同樣也有多個信號源與混音器相連,并且事先都經(jīng)過了增益調(diào)
節(jié)。當輸出混音器對所有的模擬信號進行了混合之后,通常還會有一個總控增益調(diào)節(jié)器來控制輸出聲音的
大小,此外還有一些音調(diào)控制器來調(diào)節(jié)輸出聲音的音調(diào)。經(jīng)過輸出混音器處理后的信號也是模擬信號,它
們最終會被送給喇叭或者其它的模擬輸出設備。對混音器的編程包括如何設置增益控制器的級別,以及怎
樣在不同的音源間進行切換,這些操作通常來講是不連續(xù)的,而且不會像錄音或者放音那樣需要占用大量
的計算機資源。由于混音器的操作不符合典型的讀/寫操作模式,因此除了open()和close()兩個系統(tǒng)調(diào)用
之外,大部分的操作都是通過ioctl()系統(tǒng)調(diào)用來完成的。與/dev/dsp 不同,/dev/mixer 允許多個應用程
序同時訪問,并且混音器的設置值會一直保持到對應的設備文件被關閉為止。
DSP 也稱為編解碼器,實現(xiàn)錄音(錄音)和放音(播放),其對應的設備文件是/dev/dsp 或/dev/sound/dsp。
OSS 聲卡驅(qū)動程序提供的/dev/dsp 是用于數(shù)字采樣和數(shù)字錄音的設備文件,向該設備寫數(shù)據(jù)即意味著激活
聲卡上的D/A 轉(zhuǎn)換器進行放音,而向該設備讀數(shù)據(jù)則意味著激活聲卡上的A/D 轉(zhuǎn)換器進行錄音。
在從DSP 設備讀取數(shù)據(jù)時,從聲卡輸入的模擬信號經(jīng)過A/D 轉(zhuǎn)換器變成數(shù)字采樣后的樣本,保存在聲卡驅(qū)
動程序的內(nèi)核緩沖區(qū)中,當應用程序通過 read()系統(tǒng)調(diào)用從聲卡讀取數(shù)據(jù)時,保存在內(nèi)核緩沖區(qū)中的數(shù)字
采樣結果將被復制到應用程序所指定的用戶緩沖區(qū)中。需要指出的是,聲卡采樣頻率是由內(nèi)核中的驅(qū)動程
序所決定的,而不取決于應用程序從聲卡讀取數(shù)據(jù)的速度。如果應用程序讀取數(shù)據(jù)的速度過慢,以致低于
聲卡的采樣頻率,那么多余的數(shù)據(jù)將會被丟棄(即overflow);如果讀取數(shù)據(jù)的速度過快,以致高于聲卡
的采樣頻率,那么聲卡驅(qū)動程序?qū)枞切┱埱髷?shù)據(jù)的應用程序,直到新的數(shù)據(jù)到來為止。
在向DSP 設備寫入數(shù)據(jù)時,數(shù)字信號會經(jīng)過D/A 轉(zhuǎn)換器變成模擬信號,然后產(chǎn)生出聲音。應用程序?qū)懭霐?shù)
據(jù)的速度應該至少等于聲卡的采樣頻率,過慢會產(chǎn)生聲音暫?;蛘咄nD的現(xiàn)象(即underflow)。如果用
戶寫入過快的話,它會被內(nèi)核中的聲卡驅(qū)動程序阻塞,直到硬件有能力處理新的數(shù)據(jù)為止。
與其它設備有所不同,聲卡通常不需要支持非阻塞(non-blocking)的I/O 操作。即便內(nèi)核OSS 驅(qū)動提供
了非阻塞的I/O 支持,用戶空間也不宜采用。
無論是從聲卡讀取數(shù)據(jù),或是向聲卡寫入數(shù)據(jù),事實上都具有特定的格式(format),如無符號8 位、單
聲道、8KHz 采樣率,如果默認值無法達到要求,可以通過ioctl()系統(tǒng)調(diào)用來改變它們。通常說來,在應
用程序中打開設備文件/dev/dsp 之后,接下去就應該為其設置恰當?shù)母袷?,然后才能從聲卡讀取或者寫入
數(shù)據(jù)。
17.3.2 mixer 接口
int register_sound_mixer(struct file_operations *fops, int dev);
上述函數(shù)用于注冊1 個混音器,第1 個參數(shù)fops 即是文件操作接口,第2 個參數(shù)dev 是設備編號,如果填
入-1,則系統(tǒng)自動分配1 個設備編號。mixer 是1 個典型的字符設備,因此編碼的主要工作是實現(xiàn)
file_operations 中的open()、ioctl()等函數(shù)。
mixer 接口file_operations 中的最重要函數(shù)是ioctl(),它實現(xiàn)混音器的不同IO 控制命令,代碼清單17.1
給出了1 個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()類似,它用于注冊1 個dsp 設備,第1 個參數(shù)fops 即是文件操作接
口,第2 個參數(shù)dev 是設備編號,如果填入-1,則系統(tǒng)自動分配1 個設備編號。dsp 也是1 個典型的字符
設備,因此編碼的主要工作是實現(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ù)處理對采樣率、量化精度、DMA 緩沖區(qū)塊大小等參數(shù)設置IO
控制命令的處理。
在數(shù)據(jù)從緩沖區(qū)拷貝到音頻控制器的過程中,通常會使用DMA,DMA 對聲卡而言非常重要。例如,在放音時,
驅(qū)動設置完DMA 控制器的源數(shù)據(jù)地址(內(nèi)存中DMA 緩沖區(qū))、目的地址(音頻控制器FIFO)和DMA 的數(shù)據(jù)
長度,DMA 控制器會自動發(fā)送緩沖區(qū)的數(shù)據(jù)填充FIFO,直到發(fā)送完相應的數(shù)據(jù)長度后才中斷一次。
在OSS 驅(qū)動中,建立存放音頻數(shù)據(jù)的環(huán)形緩沖區(qū)(ring buffer)通常是值得推薦的方法。此外,在OSS 驅(qū)
動中,一般會將1 個較大的DMA 緩沖區(qū)分成若干個大小相同的塊(這些塊也被稱為“段”,即fragment),
驅(qū)動程序使用DMA 每次在聲音緩沖區(qū)和聲卡之間搬移一個fragment。在用戶空間,可以使用ioctl()系統(tǒng)
調(diào)用來調(diào)整塊的大小和個數(shù)。
除了read()、write()和ioctl()外,dsp 接口的poll()函數(shù)通常也需要被實現(xiàn),以向用戶反饋目前能否讀
寫DMA 緩沖區(qū)。
在OSS 驅(qū)動初始化過程中,會調(diào)用register_sound_dsp()和register_sound_mixer()注冊dsp 和mixer 設
備;在模塊卸載的時候,會調(diào)用如代碼清單17.2。
代碼清單17.2 OSS 驅(qū)動初始化注冊dsp 和mixer 設備
1 static int xxx_init(void)
2 {
3 struct xxx_state *s = &xxx_state;
4 ...
5 //注冊dsp 設備
6 if ((audio_dev_dsp = register_sound_dsp(&xxx_audio_fops, - 1)) < 0)
7 goto err_dev1;
8 //設備mixer 設備
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 設備接口
17 unregister_sound_dsp(audio_dev_dsp);
18 unregister_sound_mixer(audio_dev_mixer);
19 ...
20 }
根據(jù)17.3.2 和17.3.3 節(jié)的分析,可以畫出一個Linux OSS 驅(qū)動結構的簡圖,如圖17.4 所示。
圖17.4 Linux OSS 驅(qū)動結構
17.3.4 OSS 用戶空間編程
1、DSP 編程
對OSS 驅(qū)動聲卡的編程使用Linux 文件接口函數(shù),如圖17.5,DSP 接口的操作一般包括如下幾個步驟:
① 打開設備文件/dev/dsp。
采用何種模式對聲卡進行操作也必須在打開設備時指定,對于不支持全雙工的聲卡來說,應該使用只讀或
者只寫的方式打開,只有那些支持全雙工的聲卡,才能以讀寫的方式打開,這還依賴于驅(qū)動程序的具體實
現(xiàn)。Linux 允許應用程序多次打開或者關閉與聲卡對應的設備文件,從而能夠很方便地在放音狀態(tài)和錄音
狀態(tài)之間進行切換。
② 如果有需要,設置緩沖區(qū)大小。
運行在Linux 內(nèi)核中的聲卡驅(qū)動程序?qū)iT維護了一個緩沖區(qū),其大小會影響到放音和錄音時的效果,使用
ioctl()系統(tǒng)調(diào)用可以對它的尺寸進行恰當?shù)脑O置。調(diào)節(jié)驅(qū)動程序中緩沖區(qū)大小的操作不是必須的,如果沒
有特殊的要求,一般采用默認的緩沖區(qū)大小也就可以了。如果想設置緩沖區(qū)的大小,則通常應緊跟在設備
文件打開之后,這是因為對聲卡的其它操作有可能會導致驅(qū)動程序無法再修改其緩沖區(qū)的大小。
③ 設置聲道(channel)數(shù)量。
根據(jù)硬件設備和驅(qū)動程序的具體情況,可以設置為單聲道或者立體聲。
④ 設置采樣格式和采樣頻率
采樣格式包括AFMT_U8(無符號8 位)、AFMT_S8(有符號8 位)、AFMT_U16_LE(小端模式,無符號16 位)、
AFMT_U16_BE(大端模式,無符號16 位)、AFMT_MPEG、AFMT_AC3 等。使用SNDCTL_DSP_SETFMT IO 控制命
令可以設置采樣格式。
對于大多數(shù)聲卡來說,其支持的采樣頻率范圍一般為5kHz 到44.1kHz 或者48kHz,但并不意味著該范圍內(nèi)
的所有連續(xù)頻率都會被硬件支持,在Linux 下進行音頻編程時最常用到的幾種采樣頻率是11025Hz、
16000Hz、22050Hz、32000Hz 和44100Hz。使用SNDCTL_DSP_SPEED IO 控制命令可以設置采樣頻率。
⑤ 讀寫/dev/dsp 實現(xiàn)播放或錄音。
圖17.5 OSS dsp 接口用戶空間操作流程
代碼清單17.3 的程序?qū)崿F(xiàn)了利用/dev/dsp 接口進行聲音錄制和播放的過程,它的功能是先錄制幾秒鐘音
頻數(shù)據(jù),將其存放在內(nèi)存緩沖區(qū)中,然后再進行放音。
代碼清單17.3 OSS DSP 接口應用編程范例
1 #include <unistd.h>
2 #include <fcntl.h>
3 #include <sys/types.h>
4 #include <sys/ioctl.h>
5 #include <stdlib.h>
6 #include <stdio.h>
7 #include <linux/soundcard.h>
8 #define LENGTH 3 /* 存儲秒數(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; /* 聲音設備的文件描述符 */
17 int arg; /* 用于ioctl 調(diào)用的參數(shù) */
18 int status; /* 系統(tǒng)調(diào)用的返回值 */
19 /* 打開聲音設備 */
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ù) */
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ù)目 */
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 /* 設置采樣率 */
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ù)錄音前等待放音結束 */
57 status = ioctl(fd, SOUND_PCM_SYNC, 0);
58 if (status == - 1)
59 perror("SOUND_PCM_SYNC ioctl failed");
60 }
61 }
2、mixer 編程
聲卡上的混音器由多個混音通道組成,它們可以通過驅(qū)動程序提供的設備文件/dev/mixer 進行編程。對混
音器的操作一般都通過ioctl()系統(tǒng)調(diào)用來完成,所有控制命令都以SOUND_MIXER 或者MIXER 開頭,表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 麥克風輸入
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 輸入
對聲卡的輸入增益和輸出增益進行調(diào)節(jié)是混音器的一個主要作用,目前大部分聲卡采用的是8 位或者16 位
的增益控制器,聲卡驅(qū)動程序會將它們變換成百分比的形式,也就是說無論是輸入增益還是輸出增益,其
取值范圍都是從0 到100。
? SOUND_MIXER_READ 宏
在進行混音器編程時,可以使用 SOUND_MIXER_READ 宏來讀取混音通道的增益大小,例如如下代碼可以獲
得麥克風的輸入增益:
ioctl(fd, SOUND_MIXER_READ(SOUND_MIXER_MIC), &vol);
對于只有一個混音通道的單聲道設備來說,返回的增益大小保存在低位字節(jié)中。而對于支持多個混音通道
的雙聲道設備來說,返回的增益大小實際上包括兩個部分,分別代表左、右兩個聲道的值,其中低位字節(jié)
保存左聲道的音量,而高位字節(jié)則保存右聲道的音量。下面的代碼可以從返回值中依次提取左右聲道的增
益大?。?/span>
int left, right;
left = vol & 0xff;
right = (vol & 0xff00) >> 8;
? SOUND_MIXER_WRITE 宏
如果想設置混音通道的增益大小,則可以通過SOUND_MIXER_WRITE 宏來實現(xiàn),例如下面的語句可以用來設
置麥克風的輸入增益:
vol = (right << 8) + left;
ioctl(fd, SOUND_MIXER_WRITE(SOUND_MIXER_MIC), &vol);
? 查詢Mixer 信息
聲卡驅(qū)動程序提供了多個ioctl()系統(tǒng)調(diào)用來獲得混音器的信息,它們通常返回一個整型的位掩碼,其中
每一位分別代表一個特定的混音通道,如果相應的位為1,則說明與之對應的混音通道是可用的。
通過 SOUND_MIXER_READ_DEVMASK 返回的位掩碼查詢出能夠被聲卡支持的每一個混音通道,而通過
SOUND_MIXER_READ_RECMAS 返回的位掩碼則可以查詢出能夠被當作錄音源的每一個通道。例如,如下代碼
可用來檢查CD 輸入是否是一個有效的混音通道:
ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask);
if (devmask & SOUND_MIXER_CD)
printf("The CD input is supported");
如下代碼可用來檢查CD 輸入是否是一個有效的錄音源:
ioctl(fd, SOUND_MIXER_READ_RECMASK, &recmask);
if (recmask & SOUND_MIXER_CD)
printf("The CD input can be a recording source");
大多數(shù)聲卡提供了多個錄音源,通過 SOUND_MIXER_READ_RECSRC 可以查詢出當前正在使用的錄音源,同一
時刻可使用2 個或2 個以上的錄音源,具體由聲卡硬件本身決定。相應地,使用 SOUND_MIXER_WRITE_RECSRC
可以設置聲卡當前使用的錄音源,如下代碼可以將CD 輸入作為聲卡的錄音源使用:
devmask = SOUND_MIXER_CD;
ioctl(fd, SOUND_MIXER_WRITE_RECSRC, &devmask);
此外,所有的混音通道都有單聲道和雙聲道的區(qū)別,如果需要知道哪些混音通道提供了對立體聲的支持,
可以通過SOUND_MIXER_READ_STEREODEVS 來獲得。
代碼清單17.4 的程序?qū)崿F(xiàn)了利用/dev/mixer 接口對混音器進行編程的過程,該程序可對各種混音通道的
增益進行調(diào)節(jié)。
代碼清單17.4 OSS mixer 接口應用編程范例
1 #include <unistd.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <sys/ioctl.h>
5 #include <fcntl.h>
6 #include <linux/soundcard.h>
7 /* 用來存儲所有可用混音設備的名稱 */
8 const char *sound_device_names[] = SOUND_DEVICE_NAMES;
9 int fd; /* 混音設備所對應的文件描述符 */
10 int devmask, stereodevs; /* 混音器信息對應的bit 掩碼 */
11 char *name;
12 /* 顯示命令的使用方法及所有可用的混音設備 */
13 void usage()
14 {
15 int i;
16 fprintf(stderr, "usage: %s <device> <left-gain%%> <right-gain%%>/n"
17 "%s <device> <gain%%>/n/n""Where <device> is one of:/n", name, name);
18 for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
19 if ((1 << i) &devmask)
20 /* 只顯示有效的混音設備 */
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; /* 增益設置 */
29 int status; /* 系統(tǒng)調(diào)用的返回值 */
30 int device; /* 選用的混音設備 */
31 char *dev; /* 混音設備的名稱 */
32 int i;
33 name = argv[0];
34 /* 以只讀方式打開混音設備 */
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 /* 確定即將用到的混音設備 */
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 /* 沒有找到匹配項 */
61 fprintf(stderr, "%s is not a valid mixer device/n", dev);
62 usage();
63 }
64 /* 查找到有效的混音設備 */
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 /* 左、右聲道設為相等 */
76 left = atoi(argv[2]);
77 right = atoi(argv[2]);
78 }
79
80 /* 對非立體聲設備給出警告信息 */
81 if ((left != right) && !((1 << i) &stereodevs))
82 {
83 fprintf(stderr, "warning: %s is not a stereo device/n", dev);
84 }
85
86 /* 將兩個聲道的值合到同一變量中 */
87 level = (right << 8) + left;
88
89 /* 設置增益 */
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ū)動返回的左右聲道的增益 */
97 left = level &0xff;
98 right = (level &0xff00) >> 8;
99 /* 顯示實際設置的增益 */
100 fprintf(stderr, "%s gain set to %d%% / %d%%/n", dev, left, right);
101 /* 關閉混音設備 */
102 close(fd);
103 return 0;
104 }
編譯上述程序為可執(zhí)行文件mixer,執(zhí)行./mixer <device> <left-gain%> <right-gain%>或./mixer
<device> <gain%>可設置增益,device 可以是vol、pcm、speaker、line、mic、cd、igain、line1、phin、
video。
17.4 Linux ALSA 音頻設備驅(qū)動
17.4.1 ALSA 的組成
雖然OSS 已經(jīng)非常成熟,但它畢竟是一個沒有完全開放源代碼的商業(yè)產(chǎn)品,而ALSA (Advanced Linux Sound
Architecture)恰好彌補了這一空白,它符合GPL,是在Linux 下進行音頻編程時另一種可供選擇的聲卡
驅(qū)動體系結構,其官方網(wǎng)站為
http://www.alsa-project.org/
。ALSA 除了像OSS 那樣提供了一組內(nèi)核驅(qū)動
程序模塊之外,還專門為簡化應用程序的編寫提供了相應的函數(shù)庫,與OSS 提供的基于ioctl 的原始編程
接口相比,ALSA 函數(shù)庫使用起來要更加方便一些。ALSA 的主要特點有:
? 支持多種聲卡設備
? 模塊化的內(nèi)核驅(qū)動程序
? 支持SMP 和多線程
? 提供應用開發(fā)函數(shù)庫(alsa-lib)以簡化應用程序開發(fā)
? 支持OSS API,兼容OSS 應用程序
ALSA 具有更加友好的編程接口,并且完全兼容于OSS,對應用程序員來講無疑是一個更佳的選擇。ALSA 系
統(tǒng)包括驅(qū)動包alsa-driver、開發(fā)包alsa-libs、開發(fā)包插件alsa-libplugins、設置管理工具包alsa-utils、
其他聲音相關處理小程序包alsa-tools、特殊音頻固件支持包alsa- firmware、OSS 接口兼容模擬層工具
alsa-oss 共7 個子項目,其中只有驅(qū)動包是必需的。
alsa-driver 指內(nèi)核驅(qū)動程序,包括硬件相關的代碼和一些公共代碼,非常龐大,代碼總量達數(shù)十萬行;
alsa-libs 指用戶空間的函數(shù)庫,提供給應用程序使用,應用程序應包含頭文件asoundlib.h,并使用共享
庫libasound.so;alsa-utils 包含一些基于ALSA 的用于控制聲卡的應用程序,如alsaconf(偵測系統(tǒng)中
聲卡并寫一個適合的ALSA 配置文件)、alsactl(控制ALSA 聲卡驅(qū)動的高級設置)、alsamixer(基于ncurses
的混音器程序)、amidi(用于讀寫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)
? 定時器接口(Timer Interface,/dev/snd/timer)
和OSS 類似,上述接口也以文件的方式被提供,不同的是這些接口被提供給alsa-lib 使用,而不是直接
給應用程序使用的。應用程序最好使用alsa-lib,或者更高級的接口,比如jack 提供的接口。
圖17.6 給出了ALSA 聲卡驅(qū)動與用戶空間體系結構的簡圖,從中可以看出ALSA 內(nèi)核驅(qū)動與用戶空間庫及
OSS 之間的關系。
圖17.6 ALSA 體系結構
17.4.1 card 和組件管理
對于每個聲卡而言,必須創(chuàng)建1 個“card”實例。card 是聲卡的“總部”,它管理這個聲卡上的所有設備
(組件),如PCM、mixers、MIDI、synthesizer 等。因此,card 和組件是ALSA 聲卡驅(qū)動中的主要組成元
素。
1、創(chuàng)建card
struct snd_card *snd_card_new(int idx, const char *xid,
struct module *module, int extra_size);
idx 是card 索引號、xid 是標識字符串、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);
當card 被創(chuàng)建后,設備(組件)能夠被創(chuàng)建并關聯(lián)于該card。第1 個參數(shù)是snd_card_new()創(chuàng)建的card
指針,第2 個參數(shù)type 指的是device-level 即設備類型,形式為SNDRV_DEV_XXX,包括SNDRV_DEV_CODEC、
SNDRV_DEV_CONTROL 、SNDRV_DEV_PCM 、SNDRV_DEV_RAWMIDI 等,用戶自定義設備的device-level 是
SNDRV_DEV_LOWLEVEL,ops 參數(shù)是1 個函數(shù)集(定義為snd_device_ops 結構體)的指針,device_data 是
設備數(shù)據(jù)指針,注意函數(shù)snd_device_new()本身不會分配設備數(shù)據(jù)的內(nèi)存,因此應事先分配。
3、組件釋放
每個ALSA 預定義的組件在構造時需調(diào)用snd_device_new(),而每個組件的析構方法則在函數(shù)集中被包含。
對于PCM、AC97 此類預定義組件,我們不需關心它們的析構,而對于自定義的組件,則需要填充
snd_device_ops 中的析構函數(shù)指針dev_free,這樣,當snd_card_free()被調(diào)用時,組件將自動被釋放。
4、芯片特定的數(shù)據(jù)(Chip-Specific Data)
芯片特定的數(shù)據(jù)一般以struct xxxchip 結構體形式組織,這個結構體中包含芯片相關的I/O 端口地址、資
源指針、中斷號等,其意義等同于字符設備驅(qū)動中的file->private_data。定義芯片特定的數(shù)據(jù)主要有2
種方法,一種方法是將sizeof(struct xxxchip)傳入snd_card_new()的extra_size 參數(shù),它將自動成員
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ù)結構體
2 {
3 ...
4 };
5 card = snd_card_new(index, id, THIS_MODULE, sizeof(struct
6 xxxchip)); //創(chuà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 個參數(shù),并動態(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 結構體中,應該包括聲卡指針:
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, //組件析構
18 };
19 ...
20 //創(chuàng)建自定義組件
21 snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
22 //在析構函數(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、注冊/釋放聲卡
當snd_card 被準備好以后,可使用snd_card_register()函數(shù)注冊這個聲卡:
int snd_card_register(struct snd_card *card)
對應的snd_card_free()完成相反的功能:
int snd_card_free(struct snd_card *card);
17.4.2 PCM 設備
每個聲卡最多可以有4 個PCM 實例,1 個PCM 實例對應1 個設備文件。PCM 實例由PCM 放音和錄音流組成,
而每個PCM 流又由1 個或多個PCM 子流組成。有的聲卡支持多重放音功能,例如,emu10k1 包含1 個32 個
立體聲子流的PCM 放音設備。
1、PCM 實例構造
int snd_pcm_new(struct snd_card *card, char *id, int device,
int playback_count, int capture_count, struct snd_pcm ** rpcm);
第1 個參數(shù)是card 指針,第2 個是標識字符串,第3 個是PCM 設備索引(0 表示第1 個PCM 設備),第4
和第5 個分別為放音和錄音設備的子流數(shù)。當存在多個子流時,需要恰當?shù)靥幚韔pen()、close()和其它
函數(shù)。在每個回調(diào)函數(shù)中,可以通過snd_pcm_substream 的number 成員得知目前操作的究竟是哪個子流,
如:
struct snd_pcm_substream *substream;
int index = substream->number;
一種習慣的做法是在驅(qū)動中定義1 個PCM“構造函數(shù)”,負責PCM 實例的創(chuàng)建,如代碼清單17.7。
代碼清單17.7 PCM 設備“構造函數(shù)”
1 static int __devinit snd_xxxchip_new_pcm(struct xxxchip *chip)
2 {
3 struct snd_pcm *pcm;
4 int err;
5 //創(chuàng)建PCM 實例
6 if ((err = snd_pcm_new(chip->card, "xxx Chip", 0, 1, 1, &pcm)) < 0)
7 return err;
8 pcm->private_data = chip; //置pcm->private_data 為芯片特定數(shù)據(jù)
9 strcpy(pcm->name, "xxx Chip");
10 chip->pcm = pcm;
11 ...
12 return 0;
13 }
2、設置PCM 操作
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, struct snd_pcm_ops *ops);
第1 個參數(shù)是snd_pcm 的指針,第2 個參數(shù)是SNDRV_PCM_STREAM_PLAYBACK 或SNDRV_PCM_STREAM_CAPTURE,
而第3 個參數(shù)是PCM 操作結構體snd_pcm_ops,這個結構體的定義如代碼清單17.8。
代碼清單17.8 snd_pcm_ops 結構體
1 struct snd_pcm_ops
2 {
3 int (*open)(struct snd_pcm_substream *substream);//打開
4 int (*close)(struct snd_pcm_substream *substream);//關閉
5 int (*ioctl)(struct snd_pcm_substream * substream,
6 unsigned int cmd, void *arg);//io 控制
7 int (*hw_params)(struct snd_pcm_substream *substream,
8 struct snd_pcm_hw_params *params);//硬件參數(shù)
9 int (*hw_free)(struct snd_pcm_substream *substream); //資源釋放
10 int (*prepare)(struct snd_pcm_substream *substream);//準備
11 //在PCM 被開始、停止或暫停時調(diào)用
12 int (*trigger)(struct snd_pcm_substream *substream, int cmd);
13 snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);// 當前緩沖區(qū)的硬件位置
14 //緩沖區(qū)拷貝
15 int (*copy)(struct snd_pcm_substream *substream, int channel,
16 snd_pcm_uframes_t pos,
17 void __user *buf, snd_pcm_uframes_t count);
18 int (*silence)(struct snd_pcm_substream *substream, int channel,
19 snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
20 struct page *(*page)(struct snd_pcm_substream *substream,
21 unsigned long offset);
22 int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
23 int (*ack)(struct snd_pcm_substream *substream);
24 };
snd_pcm_ops 中的所有操作都需事先通過snd_pcm_substream_chip()獲得xxxchip 指針,例如:
int xxx()
{
struct xxxchip *chip = snd_pcm_substream_chip(substream);
...
}
當1 個PCM 子流被打開時,snd_pcm_ops 中的open()函數(shù)將被調(diào)用,在這個函數(shù)中,至少需要初始化
runtime->hw 字段,代碼清單17.9 給出了open()函數(shù)的范例。
代碼清單17.9 snd_pcm_ops 結構體中open()函數(shù)
1 static int snd_xxx_open(struct snd_pcm_substream *substream)
2 {
3 //從子流獲得xxxchip 指針
4 struct xxxchip *chip = snd_pcm_substream_chip(substream);
5 //獲得PCM 運行時信息指針
6 struct snd_pcm_runtime *runtime = substream->runtime;
7 ...
8 //初始化runtime->hw
9 runtime->hw = snd_xxxchip_playback_hw;
10 return 0;
11 }
上述代碼中的snd_xxxchip_playback_hw 是預先定義的硬件描述。在open()函數(shù)中,可以分配1 段私有數(shù)
據(jù)。如果硬件配置需要更多的限制,也需設置硬件限制。
當PCM 子流被關閉時,close()函數(shù)將被調(diào)用。如果open()函數(shù)中分配了私有數(shù)據(jù),則在close()函數(shù)中應
該釋放substream 的私有數(shù)據(jù),代碼清單17.10 給出了close()函數(shù)的范例。
代碼清單17.10 snd_pcm_ops 結構體中close()函數(shù)
1 static int snd_xxx_close(struct snd_pcm_substream *substream)
2 {
3 //釋放子流私有數(shù)據(jù)
4 kfree(substream->runtime->private_data);
5 //...
6 }
驅(qū)動中通??梢越osnd_pcm_ops 的ioctl()成員函數(shù)傳遞通用的snd_pcm_lib_ioctl()函數(shù)。
snd_pcm_ops 的hw_params()成員函數(shù)將在應用程序設置硬件參數(shù)(PCM 子流的周期大小、緩沖區(qū)大小和格
式等)的時候被調(diào)用,它的形式如下:
static int snd_xxx_hw_params(struct snd_pcm_substream *substream,struct snd_pcm_hw_params
*hw_params);
在這個函數(shù)中,將完成大量硬件設置,甚至包括緩沖區(qū)分配,這時可調(diào)用如下輔助函數(shù):
snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
僅當DMA 緩沖區(qū)已被預先分配的情況下,上述調(diào)用才可成立。
與hw_params()對應的函數(shù)是hw_free(),它釋放由hw_params()分配的資源,例如,通過如下調(diào)用釋放
snd_pcm_lib_malloc_pages()緩沖區(qū):
snd_pcm_lib_free_pages(substream);
當PCM 被“準備”時,prepare()函數(shù)將被調(diào)用,在其中可以設置采樣率、格式等。prepare()函數(shù)與
hw_params()函數(shù)的不同在于對prepare()的調(diào)用發(fā)生在snd_pcm_prepare()每次被調(diào)用的時候。prepare()
的形式如下:
static int snd_xxx_prepare(struct snd_pcm_substream *substream);
trigger()成員函數(shù)在PCM 被開始、停止或暫停時調(diào)用,函數(shù)的形式如下:
static int snd_xxx_trigger(struct snd_pcm_substream *substream, int cmd);
cmd 參數(shù)定義了具體的行為, 在trigger() 成員函數(shù)中至少要處理SNDRV_PCM_TRIGGER_START 和
SNDRV_PCM_TRIGGER_STOP 命令,如果PCM 支持暫停,還應處理SNDRV_PCM_TRIGGER_PAUSE_PUSH 和
SNDRV_PCM_TRIGGER_PAUSE_RELEASE 命令。如果設備支持掛起/恢復,當能量管理狀態(tài)發(fā)生變化時將處理
SNDRV_PCM_TRIGGER_SUSPEND 和SNDRV_PCM_TRIGGER_RESUME 這2 個命令。注意trigger()函數(shù)是原子的,
中途不能睡眠。代碼清單17.11 給出了1 個trigger()函數(shù)的范例。
代碼清單17.11 snd_pcm_ops 結構體中trigger()函數(shù)
1 static int snd_xxx_trigger(struct snd_pcm_substream *substream, int cmd)
2 {
3 switch (cmd)
4 {
5 case SNDRV_PCM_TRIGGER_START:
6 // 開啟PCM 引擎
7 break;
8 case SNDRV_PCM_TRIGGER_STOP:
9 // 停止PCM 引擎
10 break;
11 ...//其它命令
12 default:
13 return - EINVAL;
14 }
15 }
pointer()函數(shù)用于PCM 中間層查詢目前緩沖區(qū)的硬件位置,該函數(shù)以幀的形式返回0~buffer_size – 1
的位置(ALSA 0.5.x 中為字節(jié)形式),此函數(shù)也是原子的。
copy()和silence()函數(shù)一般可以省略,但是,當硬件緩沖區(qū)不處于常規(guī)內(nèi)存中時需要。例如,一些設備
有自己的不能被映射的硬件緩沖區(qū),這種情況下,我們不得不將數(shù)據(jù)從內(nèi)存緩沖區(qū)拷貝到硬件緩沖區(qū)。例
外,當內(nèi)存緩沖區(qū)在物理和虛擬地址上都不連續(xù)時,這2 個函數(shù)也必須被實現(xiàn)。
3、分配緩沖區(qū)
分配緩沖區(qū)的最簡單方法是調(diào)用如下函數(shù):
int snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm,
int type, void *data, size_t size, size_t max);
type 參數(shù)是緩沖區(qū)的類型,包含SNDRV_DMA_TYPE_UNKNOWN(未知)、SNDRV_DMA_TYPE_CONTINUOUS(連續(xù)
的非DMA 內(nèi)存)、SNDRV_DMA_TYPE_DEV (連續(xù)的通用設備),SNDRV_DMA_TYPE_DEV_SG(通用設備SG-buffer)
和SNDRV_DMA_TYPE_SBUS(連續(xù)的SBUS)。如下代碼將分配64KB 的緩沖區(qū):
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
snd_dma_pci_data(chip->pci),64*1024, 64*1024);
4、設置標志
在構造PCM 實例、設置操作集并分配緩沖區(qū)之后,如果有需要,應設置PCM 的信息標志,例如,如果PCM
設備只支持半雙工,則這樣定義標志:
pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
5、PCM 實例析構
PCM 實例的“析構函數(shù)”并非是必須的,因為PCM 實例會被PCM 中間層代碼自動釋放,如果驅(qū)動中分配了
一些特別的內(nèi)存空間,則必須定義“析構函數(shù)”,代碼清單17.x 給出了PCM“析構函數(shù)”與對應的“構造
函數(shù)”,“析構函數(shù)”會釋放“構造函數(shù)”中創(chuàng)建的xxx_private_pcm_data。
代碼清單17.12 PCM 設備“析構函數(shù)”
1 static void xxxchip_pcm_free(struct snd_pcm *pcm)
2 {
3 /* 從pcm 實例得到chip */
4 struct xxxchip *chip = snd_pcm_chip(pcm);
5 /* 釋放自定義用途的內(nèi)存 */
6 kfree(chip->xxx_private_pcm_data);
7 ...
8 }
9
10 static int __devinit snd_xxxchip_new_pcm(struct xxxchip *chip)
11 {
12 struct snd_pcm *pcm;
13 ...
14 /* 分配自定義用途的內(nèi)存 */
15 chip->xxx_private_pcm_data = kmalloc(...);
16 pcm->private_data = chip;
17 /* 設置“析構函數(shù)” */
18 pcm->private_free = xxxchip_pcm_free;
19 ...
20 }
上述代碼第4 行的snd_pcm_chip()從PCM 實例指針獲得xxxchip 指針,實際上它就是返回第16 行給PCM
實例賦予的xxxchip 指針。
6、PCM 信息運行時指針
當PCM 子流被打開后,PCM 運行時實例(定義為結構體snd_pcm_runtime,如代碼清單17.13)將被分配給
這個子流,這個指針通過substream->runtime 獲得。運行時指針包含各種各樣的信息:hw_params 及
sw_params 配置的拷貝、緩沖區(qū)指針、mmap 記錄、自旋鎖等,幾乎要控制PCM 的所有信息均能從中取得。
代碼清單17.13 snd_pcm_runtime 結構體
1 struct snd_pcm_runtime
2 {
3 /* 狀態(tài) */
4 struct snd_pcm_substream *trigger_master;
5 snd_timestamp_t trigger_tstamp; /* 觸發(fā)時間戳 */
6 int overrange;
7 snd_pcm_uframes_t avail_max;
8 snd_pcm_uframes_t hw_ptr_base; /* 緩沖區(qū)復位時的位置 */
9 snd_pcm_uframes_t hw_ptr_interrupt; /* 中斷時的位置*/
10 /* 硬件參數(shù) */
11 snd_pcm_access_t access; /* 存取模式 */
12 snd_pcm_format_t format; /* SNDRV_PCM_FORMAT_* */
13 snd_pcm_subformat_t subformat; /* 子格式 */
14 unsigned int rate; /* rate in Hz */
15 unsigned int channels; /* 通道 */
16 snd_pcm_uframes_t period_size; /* 周期大小 */
17 unsigned int periods; /* 周期數(shù) */
18 snd_pcm_uframes_t buffer_size; /* 緩沖區(qū)大小 */
19 unsigned int tick_time; /* tick time */
20 snd_pcm_uframes_t min_align; /* 格式對應的最小對齊*/
21 size_t byte_align;
22 unsigned int frame_bits;
23 unsigned int sample_bits;
24 unsigned int info;
25 unsigned int rate_num;
26 unsigned int rate_den;
27 /* 軟件參數(shù) */
28 struct timespec tstamp_mode; /* mmap 時間戳被更新*/
29 unsigned int period_step;
30 unsigned int sleep_min; /* 睡眠的最小節(jié)拍 */
31 snd_pcm_uframes_t xfer_align;
32 snd_pcm_uframes_t start_threshold;
33 snd_pcm_uframes_t stop_threshold;
34 snd_pcm_uframes_t silence_threshold; /* Silence 填充閾值 */
35 snd_pcm_uframes_t silence_size; /* Silence 填充大小 */
36 snd_pcm_uframes_t boundary;
37 snd_pcm_uframes_t silenced_start;
38 snd_pcm_uframes_t silenced_size;
39 snd_pcm_sync_id_t sync; /* 硬件同步ID */
40 /* mmap */
41 volatile struct snd_pcm_mmap_status *status;
42 volatile struct snd_pcm_mmap_control *control;
43 atomic_t mmap_count;
44 /* 鎖/調(diào)度 */
45 spinlock_t lock;
46 wait_queue_head_t sleep;
47 struct timer_list tick_timer;
48 struct fasync_struct *fasync;
49 /* 私有段 */
50 void *private_data;
51 void(*private_free)(struct snd_pcm_runtime *runtime);
52 /* 硬件描述 */
53 struct snd_pcm_hardware hw;
54 struct snd_pcm_hw_constraints hw_constraints;
55 /* 中斷回調(diào)函數(shù) */
56 void(*transfer_ack_begin)(struct snd_pcm_substream*substream);
57 void(*transfer_ack_end)(struct snd_pcm_substream *substream);
58 /* 定時器 */
59 unsigned int timer_resolution; /* timer resolution */
60 /* DMA */
61 unsigned char *dma_area; /* DMA 區(qū)域*/
62 dma_addr_t dma_addr; /* 總線物理地址*/
64 size_t dma_bytes; /* DMA 區(qū)域大小 */
65 struct snd_dma_buffer *dma_buffer_p; /* 被分配的緩沖區(qū) */
66 #if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
67 /* OSS 信息 */
68 struct snd_pcm_oss_runtime oss;
69 #endif
70 };
snd_pcm_runtime 中的大多數(shù)記錄對被聲卡驅(qū)動操作集中的函數(shù)是只讀的,僅僅PCM 中間層可更新或修改
這些信息,但是硬件描述、中斷回調(diào)函數(shù)、DMA 緩沖區(qū)信息和私有數(shù)據(jù)是例外的。
下面解釋snd_pcm_runtime 結構體中的幾個重要成員:
? 硬件描述
硬件描述(snd_pcm_hardware 結構體)包含了基本硬件配置的定義,需要在open()函數(shù)中賦值。runtime
實例保存的是硬件描述的拷貝而非指針,這意味著在open()函數(shù)中可以修改被拷貝的描述(runtime->hw),
例如:
struct snd_pcm_runtime *runtime = substream->runtime;
...
runtime->hw = snd_xxchip_playback_hw; /* “大眾”硬件描述 */
/* 特定的硬件描述 */
if (chip->model == VERY_OLD_ONE)
runtime->hw.channels_max = 1;
snd_pcm_hardware 結構體的定義如代碼清單17.14。
代碼清單17.14 snd_pcm_hardware 結構體
1 struct snd_pcm_hardware
2 {
3 unsigned int info; /* SNDRV_PCM_INFO_* /
4 u64 formats; /* SNDRV_PCM_FMTBIT_* */
5 unsigned int rates; /* SNDRV_PCM_RATE_* */
6 unsigned int rate_min; /* 最小采樣率 */
7 unsigned int rate_max; /* 最大采樣率 */
8 unsigned int channels_min; /* 最小的通道數(shù) */
9 unsigned int channels_max; /* 最大的通道數(shù) */
10 size_t buffer_bytes_max; /* 最大緩沖區(qū)大小 */
11 size_t period_bytes_min; /* 最小周期大小 */
12 size_t period_bytes_max; /* 最大奏曲大小 */
13 unsigned int periods_min; /* 最小周期數(shù) */
14 unsigned int periods_max; /* 最大周期數(shù) */
15 size_t fifo_size; /* FIFO 字節(jié)數(shù) */
16 };
snd_pcm_hardware 結構體中的info 字段標識PCM 設備的類型和能力,形式為SNDRV_PCM_INFO_XXX。info
字段至少需要定義是否支持mmap,當支持時,應設置SNDRV_PCM_INFO_MMAP 標志;當硬件支持interleaved
或non-interleaved 格式,應設置SNDRV_PCM_INFO_INTERLEAVED 或SNDRV_PCM_INFO_NONINTERLEAVED 標志,
如果都支持,則二者都可設置;MMAP_VALID 和BLOCK_TRANSFER 標志針對OSS mmap,只有mmap 被真正支持
時,才可設置MMAP_VALID;SNDRV_PCM_INFO_PAUSE 意味著設備可支持暫停操作,而SNDRV_PCM_INFO_RESUME
意味著設備可支持掛起/恢復操作;當PCM 子流能被同步,如同步放音和錄音流的start/stop,可設置
SNDRV_PCM_INFO_SYNC_START 標志。
formats 包含PCM 設備支持的格式,形式為SNDRV_PCM_FMTBIT_XXX,如果設備支持多種模式,應將各種模
式標志進行“或”操作。
rates 包含了PCM 設備支持的采樣率,形式如SNDRV_PCM_RATE_XXX,如果支持連續(xù)的采樣率,則傳遞
CONTINUOUS。
rate_min 和rate_max 分別定義了最大和最小的采樣率,注意要與rates 字段相符。
channel_min 和channel_max 定義了最大和最小的通道數(shù)量。
buffer_bytes_max 定義最大的緩沖區(qū)大小,注意沒有buffer_bytes_min 字段,這是因為它可以通過最小
的周期大小和最小的周期數(shù)量計算出來。
period 信息與OSS 中的fragment 對應,定義了PCM 中斷產(chǎn)生的周期。更小的周期大小意味著更多的中斷,
在錄音時,周期大小定義了輸入延遲,在放音時,整個緩沖區(qū)大小對應著輸出延遲。
PCM 可被應用程序通過alsa-lib 發(fā)送hw_params 來配置,配置信息將保存在運行時實例中。對緩沖區(qū)和周
期大小的配置以幀形式存儲,而frames_to_bytes()和 bytes_to_frames()可完成幀和字節(jié)的轉(zhuǎn)換,如:
period_bytes = frames_to_bytes(runtime, runtime->period_size);
? DMA 緩沖區(qū)信息
包含dma_area(邏輯地址)、dma_addr(物理地址)、dma_bytes(緩沖區(qū)大?。┖蚫ma_private(被ALSA
DMA 分配器使用)??梢杂蓅nd_pcm_lib_malloc_pages()實現(xiàn),ALSA 中間層會設置DMA 緩沖區(qū)信息的相關
字段,這種情況下,驅(qū)動中不能再寫這些信息,只能讀取。也就是說,如果使用標準的緩沖區(qū)分配函數(shù)
snd_pcm_lib_malloc_pages()分配緩沖區(qū),則我們不需要自己維護DMA 緩沖區(qū)信息。如果緩沖區(qū)由自己分
配,則需在hw_params()函數(shù)中管理緩沖區(qū)信息,至少需管理dma_bytes 和dma_addr,如果支持mmap,則
必須管理dma_area,對dma_private 的管理視情況而定。
? 運行狀態(tài)
通過runtime->status 可以獲得運行狀態(tài),它是snd_pcm_mmap_status 結構體的指針,例如,通過
runtime->status->hw_ptr 可以獲得目前的DMA 硬件指針。此外,通過runtime->control 可以獲得DMA 應
用指針,它指向snd_pcm_mmap_control 結構體指針。
? 私有數(shù)據(jù)
驅(qū)動中可以為子流分配一段內(nèi)存并賦值給runtime->private_data,注意不要與pcm->private_data 混淆,
后者一般指向xxxchip,而前者是在PCM 設備的open()函數(shù)中分配的動態(tài)數(shù)據(jù),如:
static int snd_xxx_open(struct snd_pcm_substream *substream)
{
struct xxx_pcm_data *data;
....
data = kmalloc(sizeof(*data), GFP_KERNEL);
substream->runtime->private_data = data; //賦值runtime->private_data
....
}
? 中斷回調(diào)函數(shù):
transfer_ack_begin()和transfer_ack_end()函數(shù)分別在snd_pcm_period_elapsed()的開始和結束時被
調(diào)用。
根據(jù)以上分析,代碼清單17.15 給出了一個完整的PCM 設備接口模板。
代碼清單17.15 PCM 設備接口模板
1 #include <sound/pcm.h>
2 ....
3 /* 放音設備硬件定義 */
4 static struct snd_pcm_hardware snd_xxxchip_playback_hw =
5 {
6 .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
7 SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID),
8 .formats = SNDRV_PCM_FMTBIT_S16_LE,
9 .rates = SNDRV_PCM_RATE_8000_48000,
10 .rate_min = 8000,
11 .rate_max = 48000,
12 .channels_min = 2,
13 .channels_max = 2,
14 .buffer_bytes_max = 32768,
15 .period_bytes_min = 4096,
16 .period_bytes_max = 32768,
17 .periods_min = 1,
18 .periods_max = 1024,
19 };
20
21 /* 錄音設備硬件定義 */
22 static struct snd_pcm_hardware snd_xxxchip_capture_hw =
23 {
24 .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
25 SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID),
26 .formats = SNDRV_PCM_FMTBIT_S16_LE,
27 .rates = SNDRV_PCM_RATE_8000_48000,
28 .rate_min = 8000,
29 .rate_max = 48000,
30 .channels_min = 2,
31 .channels_max = 2,
32 .buffer_bytes_max = 32768,
33 .period_bytes_min = 4096,
34 .period_bytes_max = 32768,
35 .periods_min = 1,
36 .periods_max = 1024,
37 };
38
39 /* 放音:打開函數(shù) */
40 static int snd_xxxchip_playback_open(struct snd_pcm_substream*substream)
41 {
42 struct xxxchip *chip = snd_pcm_substream_chip(substream);
43 struct snd_pcm_runtime *runtime = substream->runtime;
44 runtime->hw = snd_xxxchip_playback_hw;
45 ... // 硬件初始化代碼
46 return 0;
47 }
48
49 /* 放音:關閉函數(shù) */
50 static int snd_xxxchip_playback_close(struct snd_pcm_substream*substream)
51 {
52 struct xxxchip *chip = snd_pcm_substream_chip(substream);
53 // 硬件相關的代碼
54 return 0;
55 }
56
57 /* 錄音:打開函數(shù) */
58 static int snd_xxxchip_capture_open(struct snd_pcm_substream*substream)
59 {
60 struct xxxchip *chip = snd_pcm_substream_chip(substream);
61 struct snd_pcm_runtime *runtime = substream->runtime;
62 runtime->hw = snd_xxxchip_capture_hw;
63 ... // 硬件初始化代碼
64 return 0;
65 }
66
67 /* 錄音:關閉函數(shù) */
68 static int snd_xxxchip_capture_close(struct snd_pcm_substream*substream)
69 {
70 struct xxxchip *chip = snd_pcm_substream_chip(substream);
71 ... // 硬件相關的代碼
72 return 0;
73 }
74 /* hw_params 函數(shù) */
75 static int snd_xxxchip_pcm_hw_params(struct snd_pcm_substream*substream, struct
76 snd_pcm_hw_params *hw_params)
77 {
78 return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
79 }
80 /* hw_free 函數(shù) */
81 static int snd_xxxchip_pcm_hw_free(struct snd_pcm_substream*substream)
82 {
83 return snd_pcm_lib_free_pages(substream);
84 }
85 /* prepare 函數(shù) */
86 static int snd_xxxchip_pcm_prepare(struct snd_pcm_substream*substream)
87 {
88 struct xxxchip *chip = snd_pcm_substream_chip(substream);
89 struct snd_pcm_runtime *runtime = substream->runtime;
90 /* 根據(jù)目前的配置信息設置硬件
91 * 例如:
92 */
93 xxxchip_set_sample_format(chip, runtime->format);
94 xxxchip_set_sample_rate(chip, runtime->rate);
95 xxxchip_set_channels(chip, runtime->channels);
96 xxxchip_set_dma_setup(chip, runtime->dma_addr, chip->buffer_size, chip
97 ->period_size);
98 return 0;
99 }
100 /* trigger 函數(shù) */
101 static int snd_xxxchip_pcm_trigger(struct snd_pcm_substream*substream, int cmd)
102 {
103 switch (cmd)
104 {
105 case SNDRV_PCM_TRIGGER_START:
106 // do something to start the PCM engine
107 break;
108 case SNDRV_PCM_TRIGGER_STOP:
109 // do something to stop the PCM engine
110 break;
111 default:
112 return - EINVAL;
113 }
114 }
115
116 /* pointer 函數(shù) */
117 static snd_pcm_uframes_t snd_xxxchip_pcm_pointer(struct snd_pcm_substream
118 *substream)
119 {
120 struct xxxchip *chip = snd_pcm_substream_chip(substream);
121 unsigned int current_ptr;
122 /*獲得當前的硬件指針*/
123 current_ptr = xxxchip_get_hw_pointer(chip);
124 return current_ptr;
125 }
126 /* 放音設備操作集 */
127 static struct snd_pcm_ops snd_xxxchip_playback_ops =
128 {
129 .open = snd_xxxchip_playback_open,
130 .close = snd_xxxchip_playback_close,
131 .ioctl = snd_pcm_lib_ioctl,
132 .hw_params = snd_xxxchip_pcm_hw_params,
133 .hw_free = snd_xxxchip_pcm_hw_free,
134 .prepare = snd_xxxchip_pcm_prepare,
135 .trigger = snd_xxxchip_pcm_trigger,
136 .pointer = snd_xxxchip_pcm_pointer,
137 };
138 /* 錄音設備操作集 */
139 static struct snd_pcm_ops snd_xxxchip_capture_ops =
140 {
141 .open = snd_xxxchip_capture_open,
142 .close = snd_xxxchip_capture_close,
143 .ioctl = snd_pcm_lib_ioctl,
144 .hw_params = snd_xxxchip_pcm_hw_params,
145 .hw_free = snd_xxxchip_pcm_hw_free,
146 .prepare = snd_xxxchip_pcm_prepare,
147 .trigger = snd_xxxchip_pcm_trigger,
148 .pointer = snd_xxxchip_pcm_pointer,
149 };
150
151 /* 創(chuàng)建1 個PCM 設備 */
152 static int __devinit snd_xxxchip_new_pcm(struct xxxchip *chip)
153 {
154 struct snd_pcm *pcm;
155 int err;
156 if ((err = snd_pcm_new(chip->card, "xxx Chip", 0, 1, 1, &pcm)) < 0)
157 return err;
158 pcm->private_data = chip;
159 strcpy(pcm->name, "xxx Chip");
160 chip->pcm = pcm;
161 /* 設置操作集 */
162 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_xxxchip_playback_ops);
163 snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_xxxchip_capture_ops);
164 /* 分配緩沖區(qū) */
165 snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
166 snd_dma_pci_data(chip - > pci), 64 *1024, 64 *1024);
167 return 0;
168 }
17.4.3 控制接口
1、control
控制接口對于許多開關(switch)和調(diào)節(jié)器(slider)而言應用相當廣泛,它能從用戶空間被存取。control
的最主要用途是mixer,所有的mixer 元素基于control 內(nèi)核API 實現(xiàn),在ALSA 中,control 用snd_kcontrol
結構體描述。
ALSA 有一個定義很好的AC97 控制模塊,對于僅支持AC97 的芯片而言,不必實現(xiàn)本節(jié)的內(nèi)容。
創(chuàng)建1 個新的control 至少需要實現(xiàn)snd_kcontrol_new 中的info()、get()和put()這3 個成員函數(shù),
snd_kcontrol_new 結構體的定義如代碼清單17.16。
代碼清單17.16 snd_kcontrol_new 結構體
1 struct snd_kcontrol_new
2 {
3 snd_ctl_elem_iface_t iface; /*接口ID,SNDRV_CTL_ELEM_IFACE_XXX */
4 unsigned int device; /* 設備號 */
5 unsigned int subdevice; /* 子流(子設備)號 */
6 unsigned char *name; /* 名稱(ASCII 格式) */
7 unsigned int index; /* 索引 */
8 unsigned int access; /* 訪問權限 */
9 unsigned int count; /* 享用元素的數(shù)量 */
10 snd_kcontrol_info_t *info;
11 snd_kcontrol_get_t *get;
12 snd_kcontrol_put_t *put;
13 unsigned long private_value;
14 };
iface 字段定義了control 的類型,形式為SNDRV_CTL_ELEM_IFACE_XXX,通常是MIXER,對于不屬于mixer
的全局控制,使用CARD。如果關聯(lián)于某類設備,則使用HWDEP、 PCM、RAWMIDI、TIMER 或SEQUENCER。
name 是名稱標識字符串,control 的名稱非常重要,因為control 的作用由名稱來區(qū)分。對于名稱相同的
control,則使用index 區(qū)分。name 定義的標準是“SOURCE DIRECTION FUNCTION”即“源 方向 功能”,
SOURCE 定義了control 的源,如“Master”、“PCM”、“CD”和“Line”,方向則為“Playback”、
“Capture”、“Bypass Playback”或“Bypass Capture”,如果方向省略,意味著playback 和capture
雙向,第3 個參數(shù)可以是“Switch”、“Volume”和“Route”等。
“SOURCE DIRECTION FUNCTION”格式的名稱例子如Master Capture Switch、PCM Playback Volume。
下面幾種control 的命名不采用“SOURCE DIRECTION FUNCTION”格式,屬于例外:
? 全局控制
“Capture Source”、 “Capture Switch”和“Capture Volume”用于全局錄音源、輸入開關和錄音音量
控制;“Playback Switch”、“Playback Volume”用于全局輸出開關和音量控制。
? 音調(diào)控制
音調(diào)控制名稱的形式為“Tone Control – XXX”,例如“Tone Control – Switch”、“Tone Control –
Bas”和“Tone Control – Center”。
? 3D 控制
3D 控制名稱的形式為“3D Control – XXX”,例如“3D Control – Switch”、“3D Control – Center”
和“3D Control – Space”。
? 麥克風增益(Mic boost)
麥克風增益被設置為“Mic Boost”或“Mic Boost (6dB)”。
snd_kcontrol_new 結構體的access 字段是訪問控制權限,形式如SNDRV_CTL_ELEM_ACCESS_XXX。
SNDRV_CTL_ELEM_ACCESS_READ 意味著只讀,這時put()函數(shù)不必實現(xiàn);SNDRV_CTL_ELEM_ACCESS_WRITE 意
味著只寫,這時get()函數(shù)不必實現(xiàn)。若control 值頻繁變化,則需定義VOLATILE 標志。當control 處于
非激活狀態(tài)時,應設置INACTIVE 標志。
private_value 字段包含1 個長整型值,可以通過它給info()、get()和put()函數(shù)傳遞參數(shù)。
2、info()函數(shù)
snd_kcontrol_new 結構體中的info()函數(shù)用于獲得該control 的詳細信息,該函數(shù)必須填充傳遞給它的第
2 個參數(shù)snd_ctl_elem_info 結構體,info()函數(shù)的形式如下:
static int snd_xxxctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo);
snd_ctl_elem_info 結構體的定義如代碼清單17.17。
代碼清單17.17 snd_ctl_elem_info 結構體
1 struct snd_ctl_elem_info
2 {
3 struct snd_ctl_elem_id id; /* W: 元素ID */
4 snd_ctl_elem_type_t type; /* R: 值類型 - SNDRV_CTL_ELEM_TYPE_* */
5 unsigned int access; /* R: 值訪問權限(位掩碼) - SNDRV_CTL_ELEM_ACCESS_* */
6 unsigned int count; /* 值的計數(shù) */
7 pid_t owner; /* 該control 的擁有者PID */
8 union
9 {
10 struct
11 {
12 long min; /* R: 最小值 */
13 long max; /* R: 最大值 */
14 long step; /* R: 值步進 (0 可變的) */
15 } integer;
16 struct
17 {
18 long long min; /* R: 最小值 */
19 long long max; /* R: 最大值 */
20 long long step; /* R: 值步進 (0 可變的) */
21 } integer64;
22 struct
23 {
24 unsigned int items; /* R: 項目數(shù) */
25 unsigned int item; /* W: 項目號 */
26 char name[64]; /* R: 值名稱 */
27 } enumerated; /* 枚舉 */
28 unsigned char reserved[128];
29 }
30 value;
31 union
32 {
33 unsigned short d[4];
34 unsigned short *d_ptr;
35 } dimen;
36 unsigned char reserved[64-4 * sizeof(unsigned short)];
37 };
snd_ctl_elem_info 結構體的type 字段定義了control 的類型,包括BOOLEAN、INTEGER、ENUMERATED、BYTES、
IEC958 和INTEGER64。count 字段定義了這個control 中包含的元素的數(shù)量,例如1 個立體聲音量control
的count = 2。value 是1 個聯(lián)合體,其所存儲的值的具體類型依賴于type。代碼清單17.18 給出了1 個
info()函數(shù)填充snd_ctl_elem_info 結構體的范例。
代碼清單17.18 snd_ctl_elem_info 結構體中info()函數(shù)范例
1 static int snd_xxxctl_info(struct snd_kcontrol *kcontrol, struct
2 snd_ctl_elem_info *uinfo)
3 {
4 uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;//類型為BOOLEAN
5 uinfo->count = 1;//數(shù)量為1
6 uinfo->value.integer.min = 0;//最小值為0
7 uinfo->value.integer.max = 1;//最大值為1
8 return 0;
9 }
枚舉類型和其它類型略有不同,對枚舉類型,應為目前項目索引設置名稱字符串,如代碼清單17.19。
代碼清單17.19 填充snd_ctl_elem_info 結構體中枚舉類型值
1 static int snd_xxxctl_info(struct snd_kcontrol *kcontrol, struct
2 snd_ctl_elem_info *uinfo)
3 {
4 //值名稱字符串
5 static char *texts[4] =
6 {
7 "First", "Second", "Third", "Fourth"
8 };
9 uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;//枚舉類型
10 uinfo->count = 1;//數(shù)量為1
11 uinfo->value.enumerated.items = 4;//項目數(shù)量為1
12 //超過3 的項目號改為3
13 if (uinfo->value.enumerated.item > 3)
14 uinfo->value.enumerated.item = 3;
15 //為目前項目索引拷貝名稱字符串
16 strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
17 return 0;
18 }
3、get()函數(shù)
get()函數(shù)用于得到control 的目前值并返回用戶空間,代碼清單17.20 給出了get()函數(shù)的范例。
代碼清單17.20 snd_ctl_elem_info 結構體中get()函數(shù)范例
1 static int snd_xxxctl_get(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 //從xxxchip 獲得值并寫入snd_ctl_elem_value
7 ucontrol->value.integer.value[0] = get_some_value(chip);
8 return 0;
9 }
get()函數(shù)的第2 個參數(shù)的類型為snd_ctl_elem_value,其定義如代碼清單10.21。snd_ctl_elem_value
結構體的內(nèi)部也包含1 個由integer、integer64、enumerated 等組成的值聯(lián)合體,它的具體類型依賴于
control 的類型和info()函數(shù)。
代碼清單17.21 snd_ctl_elem_value 結構體
1 struct snd_ctl_elem_value
2 {
3 struct snd_ctl_elem_id id; /* W: 元素ID */
4 unsigned int indirect: 1; /* W: 使用間接指針(xxx_ptr 成員) */
5 //值聯(lián)合體
6 union
7 {
8 union
9 {
10 long value[128];
11 long *value_ptr;
12 } integer;
13 union
14 {
15 long long value[64];
16 long long *value_ptr;
17 } integer64;
18 union
19 {
20 unsigned int item[128];
21 unsigned int *item_ptr;
22 } enumerated;
23 union
24 {
25 unsigned char data[512];
26 unsigned char *data_ptr;
27 } bytes;
28 struct snd_aes_iec958 iec958;
29 }
30 value; /* 只讀 */
31 struct timespec tstamp;
32 unsigned char reserved[128-sizeof(struct timespec)];
33 };
4、put()函數(shù)
put()用于從用戶空間寫入值,如果值被改變,該函數(shù)返回1,否則返回0;如果發(fā)生錯誤,該函數(shù)返回1
個錯誤碼。代碼清單17.22 給出了1 個put()函數(shù)的范例。
代碼清單17.22 snd_ctl_elem_info 結構體中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 }
對于get()和put()函數(shù)而言,如果control 有多于1 個元素,即count>1,則每個元素都需要被返回或?qū)?/span>
入。
5、構造control
當所有事情準備好后,我們需要創(chuàng)建1 個control,調(diào)用snd_ctl_add()和snd_ctl_new1()這2 個函數(shù)來
完成,這2 個函數(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 個snd_kcontrol 并返回其指針,snd_ctl_add()函數(shù)用于將創(chuàng)建的snd_kc
ontrol 添加到對應的card 中。
6、變更通知
如果驅(qū)動中需要在中斷服務程序中改變或更新1 個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 個參數(shù)為事件掩碼(event-mask),第3 個參數(shù)為該通知的control 元素id 指針。
例如,如下語句定義的事件掩碼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ū)動工程師只需編寫少量底層的控制函數(shù)。
1、AC97 實例構造
為了創(chuàng)建1 個AC97 實例,首先需要調(diào)用snd_ac97_bus()函數(shù)構建AC97 總線及其操作,這個函數(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 個參數(shù)ops 是1 個snd_ac97_bus_ops 結構體,其定義如代碼清單17.23。
代碼清單17.23 snd_ac97_bus_ops 結構體
1 struct snd_ac97_bus_ops
2 {
3 void(*reset)(struct snd_ac97 *ac97); //復位函數(shù)
4 //寫入函數(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 };
接下來,調(diào)用snd_ac97_mixer()函數(shù)注冊混音器,這個函數(shù)的原型為:
int snd_ac97_mixer(struct snd_ac97_bus *bus, struct snd_ac97_template *template, struct snd_
ac97 **rac97);
代碼清單17.24 演示了AC97 實例的創(chuàng)建過程。
代碼清單17.24 AC97 實例的創(chuàng)建過程范例
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 //注冊混音器
16 snd_ac97_mixer(bus, &ac97, &chip->ac97);
上述代碼第1 行的snd_ac97_bus 結構體指針bus 的指針被傳入第9 行的snd_ac97_bus()函數(shù)并被賦值,c
hip->ac97 的指針被傳入第16 行的snd_ac97_mixer()并被賦值,chip->ac97 將成員新創(chuàng)建AC97 實例的指
針。
如果1 個聲卡上包含多個編解碼器,這種情況下,需要多次調(diào)用snd_ac97_mixer()并對snd_ac97 的num
成員(編解碼器序號)賦予相應的序號。驅(qū)動中可以為不同的編解碼器編寫不同的snd_ac97_bus_ops 成員
函數(shù)中,或者只是在相同的一套成員函數(shù)中通過ac97.num 獲得序號后再區(qū)分進行具體的操作。
2、snd_ac97_bus_ops 成員函數(shù)
snd_ac97_bus_ops 結構體中的read()和write()成員函數(shù)完成底層的硬件訪問,reset()函數(shù)用于復位編
解碼器,wait()函數(shù)用于編解碼器標準初始化過程中的特定等待,如果芯片要求額外的等待時間,應實現(xiàn)
這個函數(shù),init()用于完成編解碼器附加的初始化。代碼清單17.25 給出了read()和write()函數(shù)的范例。
代碼清單17.25 snd_ac97_bus_ops 結構體中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 // 將被給的寄存器值寫入codec
15 }
3、修改寄存器
如果需要在驅(qū)動中訪問編解碼器,可使用如下函數(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, uns
igned short value);
unsigned short snd_ac97_read(struct snd_ac97 *ac97, unsigned short reg);
snd_ac97_update()與void snd_ac97_write()的區(qū)別在于前者在值已經(jīng)設置的情況下不會再設置,而后者
則會再寫一次。snd_ac97_update_bits()用于更新寄存器的某些位,由mask 決定。
除此之外,還有1 個函數(shù)可用于設置采樣率:
int snd_ac97_set_rate(struct snd_ac97 *ac97, int reg, unsigned int rate);
這個函數(shù)的第2 個參數(shù)reg 可以是AC97_PCM_MIC_ADC_RATE、AC97_PCM_FRONT_DAC_RATE、AC97_PCM_LR_AD
C_RATE 和AC97_SPDIF,對于AC97_SPDIF 而言,寄存器并非真地被改變了,只是相應的IEC958 狀態(tài)位將被
更新。
4、時鐘調(diào)整
在一些芯片上,編解碼器的時鐘不是48000 而是使用PCI 時鐘以節(jié)省1 個晶體,在這種情況下,我們應該
改變bus->clock 為相應的值,例如intel8x0 和es1968 包含時鐘的自動測量函數(shù)。
5、proc 文件
ALSA AC97 接口會創(chuàng)建如/proc/asound/card0/codec97#0/ac97#0-0 和ac97#0-0+regs 這樣的proc 文件,
通過這些文件可以察看編解碼器目前的狀態(tài)和寄存器。
如果1 個chip 上有多個codecs,可多次調(diào)用snd_ac97_mixer()。
17.4.5 ALSA 用戶空間編程
ALSA 驅(qū)動的聲卡在用戶空間不宜直接使用文件接口,而應使用alsa-lib,代碼清單17.26 給出了基于ALS
A 音頻驅(qū)動的最簡單的放音應用程序。
代碼清單17.26 ALSA 用戶空間放音程序
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <alsa/asoundlib.h>
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 設備句柄
11 snd_pcm_hw_params_t *hw_params; //硬件信息和PCM 流配置
12 //打開PCM,最后1 個參數(shù)為0 意味著標準配置
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 結構體
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 //初始化訪問權限
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 //設置采樣率,如果硬件不支持我們設置的采樣率,將使用最接近的
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ù)量
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 //設置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 結構體
68 snd_pcm_hw_params_free(hw_params);
69 //完成硬件參數(shù)設置,使設備準備好
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 //寫音頻數(shù)據(jù)到PCM 設備
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 //關閉PCM 設備句柄
88 snd_pcm_close(playback_handle);
89 exit(0);
90 }
由上述代碼可以看出,ALSA 用戶空間編程的流程與17.3.4 節(jié)給出的OSS 驅(qū)動用戶空間編程的流程基本是
一致的,都經(jīng)過了“打開――設置參數(shù)――讀寫音頻數(shù)據(jù)”的過程,不同在于OSS 打開的是設備文件,設
置參數(shù)使用的是Linux ioctl()系統(tǒng)調(diào)用,讀寫音頻數(shù)據(jù)使用的是Linux read()、write()文件API,而A
LSA 則全部使用alsa-lib 中的API。
把上述代碼第80 行的snd_pcm_writei()函數(shù)替換為snd_pcm_readi()就編程了1 個最簡單的錄音程序。
代碼清單17.27 的程序打開1 個音頻接口,配置它為立體聲、16 位、44.1khz 采樣和基于interleave 的讀
寫。它阻塞等待直接接口準備好接收放音數(shù)據(jù),這時候?qū)?shù)據(jù)拷貝到緩沖區(qū)。這種設計方法使得程序很容
易移植到類似JACK、LADSPA、Coreaudio、VST 等callback 機制驅(qū)動的系統(tǒng)。
代碼清單17.27 ALSA 用戶空間放音程序(基于“中斷”)
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <errno.h>
4 #include <poll.h>
5 #include <alsa/asoundlib.h>
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 當4096 個以上幀可以傳遞時喚醒我們 */
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 /* 設置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ù)就開始播放 */
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 /* 等待,直到接口準備好傳遞數(shù)據(jù),或者1 秒超時發(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 實例1:S3C2410+UDA1341 OSS 驅(qū)動
17.5.1 S3C2410 與UDA1341 接口硬件描述
如圖17.7,S3C2410 處理器內(nèi)置了IIS 總線接口,S3C2410 的IIS 總線時鐘信號SCK 與Philip 公司的UDA
1341 的BCK 連接,字段選擇連接于WS 引腳。UDA1341 提供兩個音頻通道,分別用于輸入和輸出,對應的引
腳連接:IIS 總線的音頻輸出IISSDO 對應于UDA1341 的音頻輸入;IIS 總線的音頻輸入IISSDI 對應于UDA
1341 的音頻輸出。UDA1341 的L3 接口相當于一個混音器控制接口,可以用來控制輸入/輸出音頻信號的音
量大小、低音等。L3 接口的引腳L3MODE、L3DATA、L3CLOCK 分別連接到S3C2410 的3 個GPIO 來控制。
圖17.7 S3C2410 與UDA1341 IIS 接口連接
Philips 公司的UDA1341 支持IIS 總線數(shù)據(jù)格式,采用位元流轉(zhuǎn)換技術進行信號處理,完成聲音信號的模
數(shù)轉(zhuǎn)換,具有可編程增益放大器和數(shù)字自動增益控制器,其低功耗、低電壓的特點使其非常適合用于MD/C
D、筆記本電腦等便攜式設備。UDA1341 對外提供2 組音頻信號輸入接口,每組包括左右2 個聲道。
圖17.8 UDA1341 內(nèi)部結構
如圖17.8 所示,2 組音頻輸入在UDA1341 內(nèi)部的處理存在很大差別:第一組音頻信號輸入后經(jīng)過1 個0 d
B/6 dB 開關后采樣送入數(shù)字混音器:第二組音頻信號輸入后先經(jīng)過可編程增益放大器(PGA),然后再進
行采樣,采樣后的數(shù)據(jù)要再經(jīng)過數(shù)字自動增益控制器(AGC)送入數(shù)字混音器。設計硬件電路時選用第二組
輸入音頻信號,這樣可以通過軟件的方法實現(xiàn)對系統(tǒng)輸入音量大小的調(diào)節(jié)。顯然選用第二組可以通過L3 總
線接口控制AGC 來實現(xiàn)。另外,選擇通道2 還可以通過PGA 對從MIC 輸入的信號進行片內(nèi)放大。
S3C2410 與UDA1341 之間的IIS 接口有3 種工作方式:
? 正常傳輸模式。該模式下使用IISCON 寄存器對FIFO 進行控制,CPU 通過輪詢方式訪問FIFO 寄存器,
以完成對FIFO 緩存?zhèn)鬏敾蚪邮盏奶幚怼?/span>
? DMA 模式。通過設置IISFCON 寄存器使IIS 接口工作于這種模式。在該模式下,F(xiàn)IFO 寄存器組的控制權
掌握在DMA 控制器上,當FIFO 滿時,由DMA 控制器對FIFO 中的數(shù)據(jù)進行處理。DMA 模式的選擇由IISCON
寄存器的第4 和第5 位控制。
? 傳輸/接收模式。該模式下,IIS 數(shù)據(jù)線將通過雙通道DMA 同時接收和發(fā)送音頻數(shù)據(jù)。在OSS 驅(qū)動中,
將使用此模式。
17.5.2 注冊dsp 和mixer 接口
如代碼清單17.28,在UDA1341 OSS 驅(qū)動的模塊加載函數(shù)中,將完成如下工作:
? 初始化IIS 接口硬件,設置L3 總線對應的GPIO。
? 申請用于音頻數(shù)據(jù)傳輸?shù)腄MA 通道。
? 初始化UDA1341 到恰當?shù)墓ぷ髂J健?/span>
? 注冊dsp 和mixer 接口。
代碼清單17.28 UDA1341 OSS 驅(qū)動模塊加載函數(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 /* 設置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 /* 注冊dsp 和mixer 設備接口 */
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ū)動的模塊卸載函數(shù)中,將完成與模塊加載函數(shù)相反的工作,如代碼清單17.29。
代碼清單17.29 UDA1341 OSS 驅(qū)動模塊卸載函數(shù)
1 void __exit s3c2410_uda1341_exit(void)
2 {
3 //注銷dsp 和mixer 設備接口
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ū)動的ioctl()函數(shù)處理多個mixer 命令,如SOUND_MIXER_INFO、SOUND_MIXER_READ_STEREO
DEVS、SOUND_MIXER_WRITE_VOLUME 等,用于獲得或設置音量和增益等信息,如代碼清單17.30 所示。
代碼清單17.30 UDA1341 OSS 驅(qū)動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://獲取設備對立體聲的支持
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: //設置音量
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: //設置增益
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ū)動中,dsp 接口的讀寫函數(shù)是核心中的核心,直接對應著錄音和放音的流程。
OSS 的讀函數(shù)存在一個與普通字符設備驅(qū)動讀函數(shù)不同的地方,那就是一般而言,對于普通字符設備驅(qū)動,
如果用戶要求讀count 個字節(jié),而實際上只有count1 字節(jié)可獲得(count1< count)時,它會將這count1
字節(jié)拷貝給用戶后即返回count1;而dsp 接口的讀函數(shù)會分次拷貝,如果第1 次不能滿足,它會等待第2
次,直到“count1 + count2 + ... = count”為止再返回count。這種設計是合理的,因為OSS 驅(qū)動應該
負責音頻數(shù)據(jù)的流量控制。代碼清單17.31 給出了UDA1341 OSS 驅(qū)動的讀函數(shù)實現(xiàn)。
代碼清單17.31 UDA1341 OSS 驅(qū)動的讀函數(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ū)動dsp 接口的寫函數(shù)與讀函數(shù)類似,一般來說,它也應該滿足用戶的所有寫需求后再返回,如代碼
清單17.32。
代碼清單17.32 UDA1341 OSS 驅(qū)動的寫函數(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: //只寫
13 case O_RDWR: //讀寫
14 break;
15 default: //只讀不合法
16 return - EPERM;
17 }
18 //設置DMA 緩沖區(qū)
19 if (!s->buffers && audio_setup_buf(s))
20 return - ENOMEM;
21
22 count &= ~0x03;
23
24 while (count > 0) //直到滿足用戶的所有寫需求
25 {
26 audio_buf_t *b = s->buf;
27 //非阻塞訪問
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 實例2:SA1100+ UDA1341 ALSA 驅(qū)動
17.6.1 card 注冊與注銷
同樣是UDA1341 芯片,如果以ALSA 體系結構來實現(xiàn)它的驅(qū)動,會和OSS 大不一樣。如17.4.1 節(jié)所言,在
模塊初始化和卸載的時候,需要注冊和注銷card,另外在模塊加載的時候,也會注冊mixer 和pcm 組件,
如代碼清單17.33。
代碼清單17.33 UDA1341 ALSA 驅(qū)動模塊初始化與卸載
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 // 注冊control(mixer)接口
21 if ((err = snd_chip_uda1341_mixer_new(card, &chip->uda1341)))
22 goto nodev;
23
24 // 注冊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 //注冊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 設備的實現(xiàn)
PCM 組件直接對應著ALSA 驅(qū)動的錄音和放音,從17.4.2 節(jié)的描述可知,驅(qū)動從需要定義對應相應的snd
_pcm_hardware 結構體進行PCM 設備硬件描述,如代碼清單17.34。
代碼清單17.34 UDA1341 ALSA 驅(qū)動PCM 接口snd_pcm_hardware 結構體
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 結構體內(nèi),UDA1341 ALSA 驅(qū)動對snd_pcm_ops 結構體的定義如
代碼清單17.35。
代碼清單17.35 UDA1341 ALSA 驅(qū)動PCM 接口snd_pcm_ops 結構體
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 組件的“構造函數(shù)”,其實現(xiàn)
如代碼清單17.36。
代碼清單17.36 UDA1341 ALSA 驅(qū)動PCM 組件構造函數(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 設備,playback 和capture 都為1 個 */
6 if ((err = snd_pcm_new(sa11xx_uda1341->card, "UDA1341 PCM", device, 1, 1, &pcm)) < 0)
7 return err;
8
9 /* 建立初始緩沖區(qū)并設置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 /* 設置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 /* 設置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 結構體的打開成員函數(shù)中,需要根據(jù)具體的子流賦值snd_pcm_runtime 的hw,如代碼清單
17.37。
代碼清單17.37 UDA1341 ALSA 驅(qū)動snd_pcm_ops 結構體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_constraint
s_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 結構體的trigger()成員函數(shù)中,控制播放和錄音的啟/停、掛起/恢復,如代碼清單17.3
8。
代碼清單17.38 UDA1341 ALSA 驅(qū)動snd_pcm_ops 結構體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://開啟PCM
15 if (stream_id == SNDRV_PCM_STREAM_CAPTURE && !s1->active) //開啟錄音,不在播放
16 {
17 s1->tx_spin = 1;
18 audio_process_dma(s1);
19 }
20 else
21 {
22 s->tx_spin = 0;
23 }
24
25 /* 被請求的流啟動 */
26 s->active = 1;
27 audio_process_dma(s);
28 break;
29 case SNDRV_PCM_TRIGGER_STOP:
30 /* 被請求的流關閉 */
31 audio_stop_dma(s);
32 if (stream_id == SNDRV_PCM_STREAM_PLAYBACK && s1->active) //在錄音時開啟播放
33 {
34 s->tx_spin = 1;
35 audio_process_dma(s); //啟動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: //恢復
59 s->active = 1;
60 s->tx_spin = 0;
61 audio_process_dma(s); //開啟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); //開啟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 結構體中其它的hw_params()、prepare()、pointer()等成員函數(shù)實現(xiàn)較為簡單,這里不再贅
述。
17.6.3 控制接口的實現(xiàn)
代碼清單17.33 第21 行調(diào)用的snd_chip_uda1341_mixer_new()可以認為是UDA1341 ALSA 驅(qū)動mixer 控制
組件的“構造函數(shù)”,其中會創(chuàng)建的控制元素的定義如代碼清單17.39,包括一些枚舉和單值元素。
代碼清單17.39 UDA1341 ALSA 驅(qū)動控制接口snd_kcontrol_new 結構體
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 給出了單值元素相關函數(shù)的實現(xiàn)。
代碼清單17.40 UDA1341 ALSA 驅(qū)動控制接口單值元素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 實例3:PXA255+AC97 ALSA 驅(qū)動
Intel 公司的XScale PXA255 是一款基于ARM5TE 內(nèi)核技術的嵌入式處理器。它提供了符合AC97 rev2.0
標準的AC97 控制單元(ACUNIT)和音頻控制連接(AC-Link)。ACUNIT 就是CODEC 控制器,它通過AC-Li
nk 連接和控制AC97 CODEC 芯片,例如Philips 公司出品的一款符合AC97 標準的多功能CODEC 芯片UCB14
00。它不僅是一枚CODEC 芯片,還集成了觸摸和能量管理兩個功能模塊,在嵌入式系統(tǒng)中應用廣泛。
AC-Link 連接了ACUNIT 和UCB1400Codec,只要對ACUNIT 寄存器操作就可以實現(xiàn)同UCBI400 之間的數(shù)據(jù)傳
輸。通過ACUNIT 還可讀寫CODEC 內(nèi)部寄存器,實現(xiàn)對音頻采樣和混音處理的控制。當然這些讀寫操作也是
經(jīng)由AC-Link 傳輸?shù)摹?/span>
Intel Xscale PXA255 提供了16 個DMA 通道,可以很方便的為外圍設備提供數(shù)據(jù)傳送。ACUNIT 也為CODEC
的立體聲輸入輸出和話筒輸入提供了獨立的16 bit 數(shù)據(jù)通道。每個通道都有一個專門的FIFO。
由于它是一個標準的AC97 設備,因此,其驅(qū)動的控制部分實現(xiàn)可以說是非常的簡單,按照17.4.4 節(jié)的要
求,需要實現(xiàn)AC97 codec 寄存器讀寫的硬件級函數(shù)pxa2xx_ac97_read()和pxa2xx_ac97_write(),并在模
塊初始化時注冊相關的AC97 組件,如代碼清單17.41。
代碼清單17.41 PXA255 連接AC97 codec ALSA 驅(qū)動控制組件
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 /* 設置首/次codec 空間 */
10 reg_addr = (ac97->num &1) ? &SAC_REG_BASE: &PAC_REG_BASE;
11 reg_addr += (reg >> 1);
12
13 /* 通過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)開啟另一個周期... */
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 /*設置首/次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; //通過ac97 link 寫
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 /* 構造pcm 組件 */
78 ret = pxa2xx_pcm_new(card, &pxa2xx_ac97_pcm_client, &pxa2xx_ac97_pcm);
79 if (ret)
80 goto err;
81
82 /* 申請中斷 */
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 /* 注冊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 總結
音頻設備接口包括PCM、IIS 和AC97 幾種,分別適用于不同的應用場合。針對音頻設備,Linux 內(nèi)核中包
含了2 類音頻設備驅(qū)動框架,OSS 和ALSA,前者包含dsp 和mixer 字符設備接口,在用戶空間的編程中,
完全使用文件操作;后者以card 和組件(pcm、mixer 等)為主線,在用戶空間的編程中不使用文件接口
而使用alsalib。
在音頻設備驅(qū)動中,幾乎必須使用DMA,而DMA 的緩沖區(qū)會被分割成一個一個的段,每次DMA 操作進行其
中的一段。OSS 驅(qū)動的阻塞讀寫具有流控能力,在用戶空間不需要進行流量方面的定時工作,但是它需要
及時的寫(播放)和讀(錄音),以免出現(xiàn)緩沖區(qū)的underflow 或overflow。
分享到:
上一篇:
基于linux2.6.32.2的遠程監(jiān)視系統(tǒng)
下一篇:
alsa聲卡框架的簡單分析
本站僅提供存儲服務,所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權內(nèi)容,請
點擊舉報
。
打開APP,閱讀全文并永久保存
查看更多類似文章
猜你喜歡
類似文章
writing
Linux ALSA聲卡驅(qū)動之四:Control設備的創(chuàng)建
[轉(zhuǎn)]雜記asla
snd_kcontrol的分析
淺析linux 2.6.30.4內(nèi)核中uda134x聲卡驅(qū)動源碼 - audio和bluetooth - gliethttp
Linux音頻驅(qū)動之二:聲卡的創(chuàng)建
更多類似文章 >>
生活服務
熱點新聞
首頁
萬象
文化
人生
生活
健康
教育
職場
理財
娛樂
藝術
上網(wǎng)
留言交流
回頂部
聯(lián)系我們
分享
收藏
點擊這里,查看已保存的文章
導長圖
關注
一鍵復制
下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!
聯(lián)系客服
微信登錄中...
請勿關閉此頁面
先別劃走!
送你5元優(yōu)惠券,購買VIP限時立減!
5
元
優(yōu)惠券
優(yōu)惠券還有
10:00
過期
馬上使用
×