ALSA 學習筆記 因為項目用的kernel為2.6.17,所以以下分析都是基於2.6.17版本,在這個版本裡,沒有asoc等。 1 整體架構 Application --------------- Alsa-lib User Space ------------------------------------- Alsa Kernel Space ------- sound driver ---------------------------------- Hardware
Application : 比如aplay ,它不是直接調用Kernel所提供的介面,而是調用ALSA-lib 的介面。所以應用程式只要#include "asound.h" 並連結libasound . 對於上面的架構,在某一時刻只能有一個程式開啟音效卡並佔有它,此時其它程式開啟的話,會返回busy.如要支援同時可以多個應用程式開啟音效卡,需要支援 混音功能,有些音效卡支援硬體混音,但大部分音效卡不支援硬體混音,需要軟體混音。這時需要ESD,pulseAudio等,架構變為: App1 App2 --------------- ESD , pulseaudio -------------------- Alsa-lib User Space ------------------------------------- Alsa Kernel Space ------- sound driver ---------------------------------- Hardware 此時,應用程式將調用ESD,pulseaudio等混音器提供的介面。對於ESD,很多程式支援,比如mplayer . 對於pulseaudio ,有相應的patch . Alsa本身也提供混音的plugin,dmix . App1 App2 --------------- Alsa-lib (dmix) User Space ------------------------------------- Alsa Kernel Space ------- sound driver ---------------------------------- Hardware 此架構和架構1,應用程式不需要做任何修改,只需要修改asound.conf 架構1的asound.conf的例子: pcm.!default { type hw card 0 } ctl.!default { type hw card 0 } 架構3的asound.conf的例子: pcm.card0 { type hw card 0 } pcm.!default { type plug slave.pcm "dmixer" } pcm.dmixer { type dmix ipc_key 1025 slave { pcm "hw:0,0" period_time 0 period_size 4096 buffer_size 16384 periods 128 rate 44100 } bindings { 0 0 1 1 } } 關於配置,可以參考這個網站: http://www.alsa-project.org/alsa-doc/alsa-lib/pcm_plugins.html 對於period_size和buffer_size,要注意,我將他們修改為1024 ,8192.在我們的平台上用dmix會出現underrun!!! 資訊。 2 ALSA kernel 2.1 目錄 Alsa-driver包括很多在開發中的驅動,以及一些2.2,2.4 linux核心版本的支援。當這些驅動穩定後,將移入alsa-kernel中,並最終在linux kernel 的sound目錄下. sound /core /oss /seq /oss /instr /drivers /mpu401 /opl3 /i2c /l3 /synth /emux /pci /(cards) /isa /(cards) /arm /ppc /sparc /usb /pcmcia /(cards) /oss core目錄是alsa的核心, core/oss目錄主要是為了支援PCM和mixer的OSS類比,rawmidi OSS類比放在alsa的rawmidi檔案裡主要是因為它相當小。這樣很多以前支援OSS 的應用程式也可以在Alsa上運行。 core/seq , alsa 音序器.core/seq/oss是alsa對OSS音序器類比。 driver目錄包含各種晶片驅動。 I2C目錄,alsa i2c組件。儘管linux 有標準的i2c層,但alsa對於一些音效卡有自己的i2c代碼,原因是音效卡僅僅需要一些簡單的操作而標準的 I2C API 對於此目的太複雜。 PCI目錄,主要是給PCI匯流排上的PCI音效卡 ISA目錄,主要是給ISA匯流排上的ISA音效卡 arm, ppc, and sparc目錄,這些架構下的某些音效卡驅動,如pxa2xx-pcm.c PXA處理器的 USB目錄,USB-audio 驅動 pcmcia, OSS目錄, OSS架構的檔案,不屬於alsa 2.2 介面 Alsa kernel為上層主要提供以下介面: 1 control interface 提供靈活的方式管理註冊的音效卡和對存在的音效卡進行查詢。 2 PCM interface 提供管理數字音訊捕捉和回放。 3 原始 MIDI 介面 一種標準電子音樂指令集。 這些 API 提供訪問音效卡上的 MIDI 匯流排。這些原始借口直接工作在 The MIDI 事件上,程式員只需要管理協議和時間。 4 Timer 介面 為支援聲音的同步事件提供訪問音效卡上的定時器。 5 音序器介面 一個比原始MIDI介面進階的MIDI編程和聲音同步高層介面。它可以處理很多的MIDI協議和定時器。 6 mixer介面 控制發送訊號和控制聲音大小的音效卡上的裝置。/dsp/mixer,OSS中存在。 我們主要關心1,2介面 2.3 音效卡的管理 2.3.1 卡 對於每一個音效卡,一個“卡”的記錄必須分配。 “卡”的記錄是音效卡的總部,它管理著音效卡上的所有的裝置(或者組件)的列表,例如PCM,Mixer,MIDI等等。 資料結構為:snd_card 其中 number : 第幾個音效卡,最大為SNDRV_CARDS 8個,對於我們的系統,只有一個音效卡的話,number為0 id : 音效卡的string devices : 裝置列表 proc_root :proc檔案的根 private_data:音效卡的私人資料 controls :音效卡的控制介面列表 還有一些電源管理等 調用snd_card_new來建立一個音效卡實體。 snd_card_new(index, id, module, extra_size); 其中extra_size為private_data記憶體空間的大小,在snd_card_new中分配。 2.3.2 裝置(組件) 卡執行個體建立後,我們可以attach一個組件(裝置)給一個卡的執行個體。在alsa驅動中,一個組件用結構snd_device對象表示。 一個組件可以是一個PCM執行個體,一個控制執行個體,一個原始MIDI介面等。它調用snd_device_new建立。 snd_device_new(card, snd_device_type_t, device_data, &ops); 在control.c ,pcm.c,info.c,Rawmidi.c以及timer.c中,都有snd_device_new的調用,分別建立類型為control,PCM等的deice. 資料結構為:snd_device struct list_head list; /* list of registered devices */ struct snd_card *card; /* card which holds this device */ snd_device_state_t state; /* state of the device */ snd_device_type_t type; /* device type */ void *device_data; /* device structure */ struct snd_device_ops *ops; /* operations */ snd_device_ops包含了註冊,unregister,free等函數。 在snd_card_new中,我們建立了一個control的device ,而snd_pcm_new建立了一個pcm的device
2.3.3 註冊與釋放 snd_card_register 調用它後,device 檔案可以被外界訪問。之前,不能安全被外界所訪問。 snd_card_free 一般在退出的時候調用,這樣將把所有的組件都自動釋放掉。 2.4 PCM介面 ALSA PCM中介層非常強大,驅動只需要實現底層的函數以訪問硬體。 每個卡最多可以有4個PCM執行個體。 一個PCM執行個體包含playback(回放)和capture(錄音)流,資料結構為:snd_pcm,其中struct snd_pcm_str streams[2]; stream[0]代表 playback,stream[1]代表capture. 每一個pcm流包括一個或者多個pcm子流, 一些音效卡支援多個playback功能。例如,emu10k1有一個PCM回放的32位立體聲子流(substream),此時,每次開啟,一個閒置子流自動選擇並 開啟,同時,如果只有一個子流存在並已經開啟了,接下來的開啟要麼被阻塞要麼返回EAGAIN,但是這些在你的驅動中不需要關心,PCM中介層會 管理這些工作。 snd_pcm_new建立一個實體, int snd_pcm_new(struct snd_card *card, char *id, int device, int playback_count, int capture_count, struct snd_pcm ** rpcm) device : 第幾個pcm實體,從0開始 playback_count: 回放子流數目 capture_count: 錄音子流數目 流的資料結構:snd_pcm_str 其中 stream 表示方向,是playback還是capture substream_count子流數目 struct snd_pcm_substream *substream;子流的指標,指向第一個子流,根據第一個,可以找到下一個(substream->next)
snd_pcm_new_stream建立一個流 ,在snd_pcm_new中被調用。
子流(substream)的資料結構:snd_pcm_substream 其中 stream 表示方向,是playback還是capture buffer_bytes_max 表示最大的環形buffer大小 dma_buffer 。。。。。 dma_max 最大 struct snd_pcm_ops *ops; 子流的操作函數,snd_pcm_set_ops會設定ops struct snd_pcm_runtime *runtime; next 下一個substream,對於只有一個playback的子流,為NULL struct snd_timer *timer; ???? snd_pcm_ops 定義了一系列硬體操作函數,比如open , ioctrl , trigger等 1 open callback : static int snd_xxx_open(struct snd_pcm_substream *substream); 這個函數在一個子流被開啟時調用(snd_pcm_open_substream函數調用) 調用關係為: 在 snd_pcm_f_ops中 .open = snd_pcm_playback_open snd_pcm_playback_open ---> snd_pcm_open_file---> snd_pcm_open_substream ---> snd_xxx_open snd_pcm_f_ops的open ,當你對/dev/snd/PCMC0D0 open時候被調用 我們的驅動主要是分配substream->runtime,並調用request_irq把中斷處理函數和runtime關聯起來。 2 close callback static int snd_xxx_close(struct snd_pcm_substream *substream); 這個函數在一個子流被關閉時調用 調用關係為: 在 snd_pcm_f_ops中 .release = snd_pcm_release snd_pcm_release ---> snd_pcm_release_substream ---> snd_xxx_close snd_pcm_f_ops的release ,當你對/dev/snd/PCMC0D0 close時候被調用 我們的驅動主要是調用free_irq把中斷處理函數和runtime釋放掉。 (是否有記憶體泄露,因為在open函數裡對runtimer 用kmalloc了,在close函數裡應該kfree(runtime)??? 3 ioctl callback 一般用snd_pcm_lib_ioctl 4 hw_params callback static int snd_xxx_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params); 調用關係為: 在 snd_pcm_f_ops中 .unlocked_ioctl = snd_pcm_playback_ioctl snd_pcm_playback_ioctl --> snd_pcm_playback_ioctl1 -->snd_pcm_common_ioctl1 ---> snd_pcm_hw_params_user ---> snd_pcm_hw_params ---> snd_xxx_hw_params snd_pcm_f_ops的unlocked_ioctl,當你對/dev/snd/PCMC0D0 進行ioctl(int fd, int command, (char *) argstruct)調用時,檔案系統的do_ioctl 會先判斷unlocked_ioctl函數是否為空白,不為空白則調用filp->f_op->unlocked_ioctl ,其中command為 SNDRV_PCM_IOCTL_HW_PARAMS . 這個函數在 應用程式設定硬體參數時被調用,也就是說,當pcm子流的buffer大小,周期大小,格式等被定義的時候. 許多硬體的設定必須在這個回呼函數中做,包括buffer的分配.buffer的分配可以調用snd_pcm_lib_malloc_pages函數. 我們的驅動好像只有buffer的分配,沒做別的處理,會不會有問題 ???? 5 hw_free callback static int snd_xxx_hw_free(struct snd_pcm_substream *substream); 釋放在 hw_params中分配的資源 6 prepare callback static int snd_xxx_prepare(struct snd_pcm_substream *substream); 調用關係為: 在 snd_pcm_f_ops中 .unlocked_ioctl = snd_pcm_playback_ioctl snd_pcm_playback_ioctl --> snd_pcm_playback_ioctl1 -->snd_pcm_common_ioctl1 ---> snd_pcm_prepare --> snd_pcm_do_prepare ---> snd_xxx_prepare IO命令為 SNDRV_PCM_IOCTL_PREPARE. 你可以在這個函數裡設定格式類型,採樣率等,它和hw_params的區別在於 prepare回呼函數在每次snd_pcm_prepare都會被調用 例如,underrun後的恢複等 在這個函數裡,你可以通過runtime記錄substream->runtime得到一些值,例如,目前的採樣率,格式類型,聲道數 目,runtime->rate, runtime->format or runtime->channels 分配的記憶體設定在 runtime->dma_area,大小和周期分別為runtime->buffer_size和runtime->period_size. 我們的驅動這個函數有問題 . 7 trigger callback static int snd_xxx_trigger(struct snd_pcm_substream *substream, int cmd); 調用關係為: 在 snd_pcm_f_ops中 .unlocked_ioctl = snd_pcm_playback_ioctl snd_pcm_playback_ioctl --> snd_pcm_playback_ioctl1 -->snd_pcm_common_ioctl1 ---> snd_pcm_drain --> snd_pcm_do_drain_init ---> snd_xxx_trigger IO命令為 SNDRV_PCM_IOCTL_DRAIN 這個callback函數是原子的,也就是不能調用會sleep的函數,trigger回呼函數應當盡量小,只是triggering DMA,其他的操作在 hw_params和 perpare裡面做. 我們的驅動實現了start,stop,SUSPEND 和 RESUME, 沒有實現pause,unpause.還調用了msleep???? 這不對 mdelay是一個讓CPU空轉,一直等待到批定的時間後才退出 msleep是讓當前進程休眠,讓出CPU給其它進程使用,等到時間到了之後再喚醒 msleep不能用於中斷上下文中 8 pointer callback static snd_pcm_uframes_t snd_xxx_pointer(struct snd_pcm_substream *substream) 該回呼函數在PCM中介層諮詢在buffer中當前硬體位置的時候被調用,位置以frames計算,範圍為0到buffer_size - 1. 調用關係為: snd_pcm_period_elapsed --> snd_pcm_update_hw_ptr_pos ---> snd_xxx_pointer 在中斷處理函數中,snd_pcm_period_elapsed被調用,然後PCM中介層更新位置,並計算剩餘空間,並喚醒睡著的線程. SNDRV_PCM_POS_XRUN ???? 9 其他的callback非強制的,我們驅動沒實現他們. 設定好硬體操作函數後,你可以預先分配buffer,調用 snd_pcm_lib_preallocate_pages_for_all 該函數將更新子流的dma_max 以及 dma_buffer , 其中dma_buffer中的area 代表記憶體地區
PCM執行個體的釋放,一般不需要,PCM中介層會自動釋放其中的記憶體的,除非你在初始化的時候,分配了一些特殊的變數(用kmalloc), 此時在退出函數裡,用kfree掉。
2.4.2. runtime 當一個PCM子流開啟時,一個PCM的runtime執行個體被分配並賦給substream-〉runtime 。 runtime保持大多數你需要控制PCM的資訊,hw_params and sw_params配置的拷貝,buffer的指標等 資料結構:snd_pcm_runtime 包括 hw :hw_params 資訊 硬體描述符(struct snd_pcm_hardware)包含了基本硬體設定的定義.首先,你將在open 回呼函數裡定義它. 需要說明的是:runtime執行個體保持著描述符的copy,不是對已經的描述符的指標,也就是說,在open 回呼函數中,你可以修改runtime->hw 根據你的需要.
2.4.3 中斷處理 中斷處理函數在音效卡驅動中的作用:更新buffer的位置並在buffer位置越過以前設定的period size時,告訴PCM中介層.調用snd_pcm_period_elapsed函數 將告訴PCM中介層. 在snd_pcm_hw_params中會對period_size進行設定. 有以下幾種音效卡類型產生中斷的方式: 1)在周期邊界產生中斷 最常用的方式 2)高頻率的timer中斷 用於那些不產生中斷的晶片 我們的實現: 對於位置pcm_buf_pos ,在prepare調用的時候設為0,在中斷的時候,每次會傳60個bytes,每次加上60,迴圈. 然後調用snd_pcm_period_elapsed函數通知上層.
在中斷中調用spin_lock ???? 為了多處理器嗎???
PCM中的file_operation : 當調用snd_pcm_new時, snd_pcm_new ---> snd_pcm_dev_register --> snd_register_device 此函數更新snd_minor結構, 而在 alsa_sound_init中,register_chrdev(major , "alsa",&snd_fops);當一個alsa主裝置開啟時,會調用snd_fops中的open函數, 也就是snd_open( 在sound.c中),而snd_open會根據snd_minor替換其file_ops,如果minor是PCM裝置,將用PCM的file_operation (snd_pcm_new中更新了) |