深入了解ALSA


Intro

任何人如果經常的使用linux機器處理音樂,那么他遲早會和ALSA打交道。ALSA是Advanced Linux Sound Architecture的簡稱,和過時的Open Sound System(OSS)比起來更強大功能更多。事實上,你可能已經不知不覺的使用了ALSA,比如ALSA的OSS模擬功能。當在web上搜索關於ALSA的答案時,我發現都是提問和自相矛盾的聲明,鮮有確切的答案。我想有兩個原因:首先,有些聲音問題不像看起來那么簡單,此外ALSA文檔簡直是一團糟。本文會嘗試解決這些聲音問題,並矯正一團糟的ALSA文檔。

在我們正式開始之前,你最好先瀏覽一下其他資源。他們有的包含一些例子程序,可以用這些程序執行調用ALSA來播放或者錄音;此外他們可能也包含了你要找的答案。

我首先特別推薦一篇信息全面的頁面http://www.sabi.co.uk/Notes/linuxSoundALSA.html, 和本文側重於深層次研究相比,它的覆蓋面更廣。

其他的資源包括:

a LINUX Journal article about basic ALSA programming 包含一些示例代碼

tutorials on the ALSA project web site 包含一些示例代碼

one of the developers' home page 比較舊

有些文檔內嵌在ALSA library的源代碼中,可以使用源碼文檔生成工具doxygen生成,使用命令make doc。也可以在線閱讀API文檔, 不幸的是它並不完整,瀏覽起來很麻煩。以我的觀點,開發者最初設想不止提供API文檔,但是結果是一樣都沒達到。如果你真得想理解ALSA如何工作的,那么你最好結合着源代碼來看這些文檔。

ALSA Concepts

聲卡和硬件設備

ALSA用cards,device和subdevices的分層結構表示audio硬件設備和他們的組件。這個分層結構是ALSA看待硬件設備結構和能力的視角。如果聲卡這個分層結構和聲卡的文檔有差別,那么可能是由於驅動沒有支持所有的功能。

ALSA cards和聲卡硬件是一一對應的。ALSA cards的主要保存每塊卡上的設備列表。一個card可以通過一個ID(字符串)或者從0開始的數字表示。

大部分ALSA硬件訪問發生在device級別。可以從0開始枚舉每個卡的devices,不同的devices可以獨立的打開和使用。典型的,聲卡和設備這兩個標識足以決定聲音信號從哪里讀取,送到哪里。

Subdevices是ALSA能夠區分的更細粒度的對象。最常見的場景是一個device的每個channel都對應一個subdevice或者總共只有一個subdevice。一個device的subdevice理論上可以單獨使用,但是在一個subdevice上播放multi-channel信號時,也會使用其余的subdevices。和device一樣,subdevices索引標識從0開始。

PCM參數和配置空間

數字化聲音有一定數目的參數:采樣率,通道數和采樣值存儲格式。如果你已經使用OSS編程,那么你可能習慣於在播放音樂文件之前設置這些參數。類似於ALSA文檔中所提到的配置空間。

現實的答案沒有那么簡單:比如一些聲卡無法結合所有支持的采樣格式,采樣率和通道數。所以這些參數不是獨立的。ALSA考慮到了這種情況,使用一個n維空間的參數集,每一維對應着采樣率,采樣格式,通道數等等。如果一個給定聲卡的參數是獨立的,那么所有合法配置就在一個n維盒子中,在這種情況下,我們需要做的就是描述每一維的取值范圍。如果參數之間不是獨立的,那么允許的配置集比較復雜了。

當一個硬件設備通過ALSA訪問,參數並不總是獨立的。進一步說,一個設備由於某些特定參數的限制,它的合法配置空間被相應的壓縮小了。這就使得我們可以使用一個較小空間而不是確定的數值。此外,還導致依賴於參數設置順序的問題。也就是說ALSA plugin能夠自動選擇最合適的硬件參數並執行格式轉換。我們在接下來的小節中會討論這個plugin和其他的plugin

ALSA devices and plugins

為了避免混淆,我們先簡單介紹一下ALSA device。這里說的ALSA device和上面提到的hardware devices完全不同。ALSA device用字符串表示。他們定義在ALSA的一個配置文件中。更復雜的是,一些標准的ALSA devices是:屬類:card,devicce,subdevice。但是硬件card和device的規范不能做為ALSA設備,事實上有些ALSA devices的參數不是硬件相關的。

不誇張的說ALSA幾乎都是由plugins組成的。不論什么時候一個player或者程序使用ALSA設備時,plugins做臟活累活。plugins的完整列表在ALSA library doxygen 文檔的pcm_plugins.html中。注意plugins列表並不等同於ALSA devices列表。一些標准devices的名字和他們使用的plugins同名,但是有些devices並不是這樣,有時不同的ALSA devices使用相同的plugin。因此本節我們會給出每一個plugin的名字,如果可能,還會給出特定硬件設備使用這個plugin允許的名字。

最重要的plugin無疑是hw plugin。它本身不做任何處理,僅僅訪問硬件驅動。如果應用選擇硬件不支持的PCM參數(sampling rate, channel count或者sample format),hw plugin返回出錯信息。因此下一個重要的plugin是'plug' plugin,plug plugin執行channel復制,采樣率轉換以及必要的重采樣。不像hw plugin被device hw:0,0使用,plug plugin對應的device命名是不同的:plughw:0,0。但二者的設備名都包含要訪問的硬件cards,device以及subdevice。(事實上,plug device也存在,也使用plug plugin,並且它的參數SLAVE指明數據要發送到哪里,因此這個plugin一定和其他的plugins鏈接到一起)

此外一個很有用的plugin是file plugin,它會把采樣數據寫到一個文件中。它有兩個ALSA devices:file 和 tee。前者有兩個參數,文件名和格式。后者傳送數據到另外一個device以便寫到一個文件中,第一個參數就是那個device。如果第二個設備有任何參數(比如 "plughw:0,0"),那么你要把他的名字用引號括起來,以防止被命令行解釋。假定你想使用第一個聲卡上的第一個設備播放聲音,你可以用如下方式獲取輸出聲音的copy。

 

[cpp]  view plain  copy
 
  1. aplay -Dtee:\'plughw:0,0\', /tmp/alsatee.out, rawxy.wav  

 

得承認這看起來沒什么意義(因為你可以簡單的copy xy.wav或者轉換為sox),然而使用這個方法,基於ALSA的movie player可以抽取聲音內容。tee's 輸出是raw 采樣數據沒有任何文件頭。file plugin也可以用來從文件中讀取數據,但是沒有預定義的設備使用這種方式。

現存的許多plugins用來mixer和rerouting channels。由於這些plugins需要大量的參數,沒有預定以的ALSA devices使用他們。route plugin是一個mixing矩陣。channels不僅僅可以被交換或者任意賦值,而且可以被混音。多個plugin僅僅允許reroute channels,但是可以有幾個slave devices,因此可以在不同聲卡的channels上播放。dmix和dshare plugins允許一個device被多個clients(player application)使用。dshare plugin把可用channels分配給需要的clients,而dmix則是把多個clients播放的內容混音到一個channels。

簡短的介紹了最重要的幾個plugins以及預定義的ALSA devices。為了使用更復雜的plugins,需要寫自己的配置文件,下一節將詳細描述配置文件。device定的例子可以參照ALSA項目documentation of its configuration file 以及list of plugins

 

Configuring ALSA

configuration files

配置文件用來定義ALSA devices。沒有這些配置文件,你就無法使用ALSA的任何功能。雖然你並不需要寫任何配置文件就可以進行簡單的播放和錄音,這是因為內建的配置文件alsa.conf包含一些devices的定義。但是如果你有特殊的需求或者碰到了問題,你可能就需要增加自己的定義了。

ALSA支持三類配置文件。第一個是alsa.conf,位於ALSA 數據目錄下,通常是/usr/share/als。這個目錄和它的子目錄包含了更多的配置文件,關於聲卡的或者特定的plugin,他們都被包含進alsa.conf。這個目錄下的配置文件通常被看作built-in,所以系統管理員和普通用戶都不應該修改它。

其他兩個配置文件也被alsa.conf包含進來,系統范圍內的配置文件存儲在/etc/asoundrc。用戶可以存儲他們自己的配置文件到HOME目錄下的.asoundrc中。每次打開ALSA devices所有的配置文件都會被重新分析。所以配置文件的變化是立即生效的,不需要重啟任何東西。

Basic Configuration file format

ALSA配置文件保存着分層結構的參數-值對。分層結構的頂層對應着ALSA提供的接口。一個接口包含着一組ALSA API功能:允許打開一個device,對接口做操作,然后關閉device。不同的接口有不同的作用,比如aplay和其他的player使用PCM接口,程序alsactl使用ctl接口。amixer使用mixer接口,amidi使用rawmidi接口。(事實上他們大部分最終都會使用ctl接口,至少部分功能)。為了讓這些程序能夠使用device,必須在相應的接口定義他們。

我給的例子大部分都是和pcm接口相關的,主要因為PCM幾乎是最重要的並且我大部分時間也都花在PCM。(PCM代表Pulse code Modulation,是把聲音數字化為采樣值流)。此外命令aplay -L 允許你列出pcm interface上的所有定義。

層次結構的第二級保存着ALSA devices的名字,可以通過這個device操作device所在的接口

讓我們看一個例子,假定你想創建一個ALSA PCM device訪問你的第一個聲卡並且做必要的格式轉換。plugin是通過plug plugin實現的自動格式轉換。在asoundrc中加入下面幾行,創建這個PCM device。

 

[cpp]  view plain  copy
 
  1. pcm.plug0 {  
  2.     type plug  
  3.     slave {  
  4.         pcm "hw:0,0"  
  5.     }  
  6. }  

 

解釋下這幾行的意思:一個新的device plug0,可以通過pcm接口訪問。這個device的數據輸出將會被plug plugin處理。這個plugin的slave是pcm device hw:0,0。這個device定義是最常用的定義方式。當然,ALSA在語法上允許一定自由度。比如,可以在參數名和值之間放一個等號,或者在賦值語句間增加分號和逗號。所以我們可以改寫上面的定義如下

 

[cpp]  view plain  copy
 
  1. pcm.plug0 = {  
  2.     type = plug;  
  3.     slave = {  
  4.         pcm = "hw:0,0" ;  
  5.     },  
  6. };  

 

如你所見, 花括號前是參數名,花括號內則是它的參數值。設置在參數體內的最后一個參數賦值可以跟着逗號或者分號。現在我們知道問什么slave PCM要使用分號了,否則slave PCM中的逗號會導致一個語法錯誤。

事實上語法還有更大的自由度。子參數名可以通過‘.’定義也可以用括號定義。第一個符號plug0是新設備的名字,也是pcm的一個參數。第二個則定義了所得的子參數。此外配置文件並使面向行的,可以使用空格作為一行的分割。所以上面的定義可以寫做如下兩種形式

 

[cpp]  view plain  copy
 
  1. pcm {  
  2.     plug0 {  
  3.         type plug slave { pcm "hw:0,0" }  
  4.     }  
  5. }  

或者

 

 

[cpp]  view plain  copy
 
  1. pcm.plug0.type = plug; pcm.plug0.slave.pcm = "hw:0,0"  

 

現在我們總結一下ALSA配置文件的機構。參數名是由字母,數字和下划線組成的(這個結論是調試的結果,實際上並沒有文檔)。參數值包含字母,數字和下划線並使用引號括起來。參數名和參數值都是大小寫敏感的。配置文件的注釋以 #開始直到本行的結束。

為了是slave的定義更清晰,我們應該單獨定義slave。這樣上面的plug0的定義應該這樣。

[cpp]  view plain  copy
 
  1. pcm_slave.slave0 {  
  2.     pcm "hw:0,0"  
  3. }  
  4. pcm.plug0 {  
  5.     type plug  
  6.     slave slave0  
  7. }  

 

配置文件還支持別名。我們可以把參數賦值為一個已存在的設備名,這個參數就是別名。

 

[cpp]  view plain  copy
 
  1. pcm.alias_plug0 = plug0  

alias_plug0和plug0都是接口pcm的一部分,注意不能定義為(pcm.alias_plug0 = pcm.plug0),因為ALSA devices的別名不能帶參數

 

現在我們已經有了大概的了解,你可以繼續閱讀http://alsa.opensrc.org/.asoundrc和ALSA library的doxygen文檔,他們提供了更多的例子。

 

Advanced configuration file features

Overriding parameters and parameter data types

如果你已經閱讀了其他的asoundrc,那么你可能已經學會了重新定義ALSA's default device如下:

 

[cpp]  view plain  copy
 
  1. pcm.!default { type hw card 0 }  

嘆號使得原來的pcm.default定義被覆蓋。這個符號可以在任意配置文件賦值中使用。現在讓我們來看看它是如何工作的,正常的賦值是增加一個葉子節點到樹結構中。如果葉子節點已經存在,那么他的值就會被覆蓋。所以如果你在asoundrc中放入下面兩行,那么缺省的聲卡就是第二個,而不是第一個

 

 

[cpp]  view plain  copy
 
  1. pcm.!default { type hw card 1 }  
  2. pcm.default.card 1  


如果你把關於default的第一行定義移除,那么你將收到一個error信息default不是一個compund。很明顯,參數有幾種數據類型,類型不能僅僅通過賦值語句就被修改。通過嘆號覆蓋一個參數,就可以改變他的類型。嘆號使得參數和它的子參數全部被移除。所以不要如下方式使用嘆號 !pcm...,這將刪除pcm interface的所有定義。

 

稍微有點使用經驗,就會發現別名實際上是一個字符串,包含他們所指向的PCM device的名字。pcm.default就是alsa.conf中定義的一個別名。

和嘆號類似,其他的前綴字符可以用來修飾參數賦值。問號?會忽略掉參數值已經存在的參數。

 

[cpp]  view plain  copy
 
  1. pcm.?default {  
  2.     type hw card 1  
  3. }  

定義第一個設備的第二個聲卡作為default device,但是如果default在之前已經定義過,那么這個聲明無效。

 

其他的兩個前綴是+和-。

Parameterised device definitions

在plugins一節中,我們碰到了很多ALSA devices需要給定參數,跟隨在device name后使用","分割。就像我上面提到的,這些device和他們后面的plugins是完全不同的東西,devices可以看作是plugins的封裝。在basic configuration section一節我們使用plug plugin定義了一個device。我們在plugin一節碰到的預定義設備和這種簡單的設備是不同的。plughw設備可以被任意的聲卡和硬件設備使用,因為我們可以使用card 和device號作為參數。盡管所有的參數化設備都是在alsa.conf中定義的,並且沒有ALSA文檔說明他們的語法,你仍然可以定義自己的devices

讓我們看一下plughw在alsa.conf中的定義,這里的plughw做了縮略

 

[cpp]  view plain  copy
 
  1. plughw {  
  2.   @args [ CARD DEV SUBDEV ]  
  3.   @args.CARD {  
  4.     type string  
  5.   }  
  6.   @args.DEV {  
  7.     type integer  
  8.   }  
  9.   @args.SUBDEV {  
  10.     type integer  
  11.   }  
  12.   type plug  
  13.   slave.pcm {  
  14.     type hw  
  15.     card $CARD  
  16.     device $DEV  
  17.     subdevice $SUBDEV  
  18.   }  
  19. }  

plughw結構的第一行定義了參數列表。這對於我們是一個新的數據類型:數組。他的元素可以一起賦值。

 

 

[cpp]  view plain  copy
 
  1. plughw {  
  2.     @args.0 CARD  
  3.     @args.1 DEV  
  4.     @args.2 SUBDEV  
  5. }  

和結構賦值一樣,元素或組賦值可以插入等號。接下來的幾行定義了每個參數的數據類型,在plughw的原始定義中,結構體定義參數時也包括了缺省的定義,如果你對此感興趣,可以看下alsa.conf。

 

 

Troubleshooting

 

Useful tools

查看聲卡設備的最簡單方法是調用

aplay -l 或者arecord -l

這兩個命令會列出可以playback和recording的聲卡,硬件設備以及子設備。

如果你對ALSA解析asoundrc有任何疑問,你可以使用命令aplay -L,打印出pcm接口的所有配置。注意前綴"pcm."沒有出現在參數名中,因為它一直是隱含的。

aplay的另外一個選項是-v,這個選項在playback時會打印出每個subdevice的硬件參數。不像其他兩個提到的選項,這個選項只能在playback時起作用。當你使用plug plugin來播放聲音,可以用這個選項來檢查是否plug做了resampling。

但是如果你使用其他的audio或者movie player時怎么辦? 可以使用/proc/ file system。當subdevice正在使用時,可以從/proc/asound/card#/pcm#p/sub#/hw_params獲取硬件參數。通過比較aplay test.wav -v和/proc/asound/card#/pcm#p/sub#/hw_params的輸出結果,我們可以知道哪些參數是模擬的。如果subdevice沒有被使用沒,那么參數文件hw_params僅僅輸出"closed"。如果在播放音樂時,顯示的是”closed“,那么說明聲音沒有流到這個subdevice。例外情況:如果一個ALSA device被一個multi-channel playback使用,那么第一個subdevice記錄總channel數,其他的則匯報為closed。如果使用了ALSA's OSS模擬,那么hw_params文件也包含OSS的參數設置。

amidi程序使用rawmidi接口,選項-l列出所有可用的MIDI devices,-L選項打印出rawmidi接口的配置。

speaker-test,當你聽不到任何聲音,想確定一下是否player有問題,那么可以使用speaker-test做為基准測試程序。這個測試程序可以順序的給每個speaker發送鈴音或者噪聲。

alsacap

alsacap是作者寫的一個小程序,用來抓取一些作者特別關注的一些硬件配置。我認為這個工具對於查找問題很有幫助。源碼地址http://www.volkerschatz.com/noise/alsacap.c, 源碼頂端的注釋有編譯說明。這個工具名意思是ALSA capability,用來顯示你的聲卡和ALSA驅動的能力。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM