Alsa音頻驅動學習筆記

   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中更新了)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章