因爲項目用的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中更新了)