學習目的:
- 分析Linux中OSS聲卡驅動框架
1、OSS聲卡驅動框架
Linux下的聲卡驅動架構主要分為OSS架構和ALSA架構,OSS全稱是Open Sound System,叫做開放式音頻系統,ALSA全稱是Advanced Linux Sound Architecture,叫做Linux高級音頻架構。
OSS架構是基於文件系統的訪問方式,對聲音的操作完成可以像對普通文件那樣執行open、read等操作。
OSS標准中有2個最基本的音頻設備:mixer(混音器)和DSP(數字信號處理器),mixer的作用將多個信號組合或者疊加到一起、可用來調整音量大小和選擇音源。對於不同聲卡來說,其混音器的作用可能各不相同。OSS驅動中,/dev/mixer設備文件是應用程序對mixer進行操作的軟件接口。DSP也稱編解碼器,實現錄音和放音的操作,其對應的設備文件是/dev/dsp和/dev/sound/dsp,向該設備寫數據即意味着激活聲卡上的D/A 轉換器進行放音,而向該設備讀數據則意味着激活聲卡上的A/D 轉換器進行錄音。
下面從內核源碼的角度來分析OSS聲卡驅動框架
內核目錄下sound/sound_core.c文件是OSS聲卡驅動的核心層,sound_core.c文件向上提供了應用程序訪問音頻設備統一的入口,向下為不同音頻設備提供了向上的注冊接口。
sound_core.c文件中的入口函數init_soundcore
static int __init init_soundcore(void) { if (register_chrdev(SOUND_MAJOR, "sound", &soundcore_fops)==-1) { ... } sound_class = class_create(THIS_MODULE, "sound"); ... return 0; }
init_soundcore中向內核注冊了一個主設備號為14的字符設備,可以看出OSS架構的聲卡驅動是將硬件作為字符設備來訪問的。當應用程序訪問音頻設備節點時,會調用到soundcore_fops結構體中成員函數。
不過,這里注冊的字符設備對應的file_operations類型結構體soundcore_fops中,只實現了.open函數
static const struct file_operations soundcore_fops= { /* We must have an owner or the module locking fails */ .owner = THIS_MODULE, .open = soundcore_open, }; int soundcore_open(struct inode *inode, struct file *file) { int chain; int unit = iminor(inode); struct sound_unit *s; const struct file_operations *new_fops = NULL; chain=unit&0x0F; ... s = __look_for_unit(chain, unit); if (s) new_fops = fops_get(s->unit_fops); ... if (new_fops) { ... const struct file_operations *old_fops = file->f_op; file->f_op = new_fops; if(file->f_op->open) err = file->f_op->open(inode,file); ... } ... }
打開音頻設備時,soundcore_open函數會根據打開設備節點的次設備號找到一個sound_unit(__look_for_unit函數根據次設備號查找sound_unit是在以chains[]數組某項為頭部的鏈表中查找的,關於這個指針數組的相關的內容后面會詳細解釋),並將找到的sound_unit中的file_operations結構體作為訪問該設備的操作函數(file->f_op = new_fops)。
由此可以看出,注冊的字符設備中的open函數僅起到了中轉作用,open設備時會找到打開的設備對應的真實的操作函數,用真實的設備節點操作函數操作設備。這樣做,起到了為訪問不同的音頻設備提供統一的入口函數的目的。
sound_unit結構體是OSS架構音頻驅動的一個核心數據結構,用來描述一個物理聲卡設備的,結構體成員信息如下:
struct sound_unit { int unit_minor; const struct file_operations *unit_fops; struct sound_unit *next; char name[32]; };
unit_minor代表這個設備的對應的次設備號;unit_fops中包含一系列操作該設備的函數;next代表sound_unit結構體是一個鏈表
sound_core.c中定義了一個長度為16的sound_unit類型的指針數組chains[],該數組每一項都表示一個鏈表頭部。如chains[0]表示mixers設備掛載的鏈表頭部,chins[3]表示DSP設備掛載鏈表頭部。每注冊一種類型的音頻設備,描述設備的sound_unit結構體會插入到對應類型頭部的鏈表中。同時,打開音頻設備時,soundcore_open函數根據打開設備節點次設備號找到對應的sound_unit也時基於chains[]數組。
次設備號的低4位代表該設備節點是屬於哪種類型音頻驅動,如次設備號為0x00、0x10、0x20、0x30代表該設備節點屬於Mixers,次設備號0x03、0x13、0x23、0x33代表設備節點屬於DSP。根據次設備號查找對應sound_unit時,直接取次設備號低4位作為chains數組索引,可以直接得到描述設備的sound_unit結構體存放的鏈表頭部,這樣大大的節省了查找鏈表的效率。
chains[n]為頭部的鏈表中的節點是在音頻驅動程序向上注冊音頻設備時插入的,不同類型的音頻設備的注冊函數也是sound_core.c文件中提供的,這些注冊函數在設備驅動程序中調用。所謂的注冊也就是根據傳入信息構造一個sound_unit,並將構造好的sound_unit插入到指定的鏈表中。
以內核中注冊一個DSP音頻設備為例,進行注冊過程分析
int register_sound_dsp(const struct file_operations *fops, int dev) { return sound_insert_unit(&chains[3], fops, dev, 3, 131, "dsp", S_IWUSR | S_IRUSR, NULL); }
使用register_sound_dsp函數注冊一個DSP音頻設備,該函數有兩個參數,一個參數是file_operations結構體指針fops,另一個是dev。fops指針傳入的是該設備的操作函數,dev代表分配的DSP的編號(一般情況下傳入-1,請求分配下一個空的DSP單元)。
register_sound_dsp函數中直接調用sound_insert_unit函數,傳入參數list = &chains[3]表示DSP音頻設備是插入到chains[3]為頭部的鏈表中,參數low = 3,top = 131表示dsp音頻設備使用的編號范圍(0x03、0x13、0x23...0x83),注冊時查找可用編號范圍內是否有可插入點,如果有就插入到該點鏈表中,並將該點作為次設備號。
static int sound_insert_unit(struct sound_unit **list, const struct file_operations *fops, int index, int low, int top, const char *name, umode_t mode, struct device *dev) { struct sound_unit *s = kmalloc(sizeof(*s), GFP_KERNEL); int r;
...
r = __sound_insert_unit(s, list, fops, index, low, top); ... device_create(sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor), s->name+6); ... }
sound_insert_unit函數動態分配了一個sound_unit結構體,然后調用了__sound_insert_unit函數。__sound_insert_unit函數根據傳入的參數,在list為頭部鏈表中找到合適位置,初始化傳入的sound_unit結構體,將初始化好的sound_unit結構體掛入到鏈表中找到的位置中。最后調用device_create在sound_class類下創建了一個設備,device_create創建的設備信息會被udev機制使用在/dev目錄下自動創建設備節點,這樣用戶程序可以使用/dev目錄下創建的設備節點對音頻設備進行訪問。
由以上分析,可以總結出OSS架構的音頻驅動框架如下圖所示:
2、小結
OSS架構聲卡驅動,將聲卡設備當做字符設備來訪問,注冊的聲卡設備的主設備號為14。OSS驅動核心層提供了各種類型聲卡設備的注冊函數,成功注冊的聲卡設備會存放到chains[n]的為頭部的鏈表中,並會為該設備在/dev目錄下創建設備節點。當應用程序通過設備節點訪問該設備,在open該設備時,根據次設備號信息從chains[minor&0xf]為頭部的鏈表中找到打開設備對應的file_operations結構體,使用該file_operations結構體中成員函數訪問該設備。
因此,我們編寫基於OSS架構聲卡驅動程序時,只需完成一些硬件相關的操作,填充適用於聲卡設備操作的file_operations結構體,然后調用OSS驅動核心層提供的設備注冊函數register_sound_xxx向內核注冊一個聲卡設備。