Alsa學習筆記

來源:互聯網
上載者:User
 

   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中更新了)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.