嵌入式Linux音頻驅動開發

1.嵌入式音頻系統硬件連接

下圖所示的嵌入式設備使用IIS將音頻數據發送給編解碼器。對編解碼器的I/O寄存器的編程通過IIC總線進行。

2.音頻體系結構-ALSA

ALSA是Advanced Linux Sound Architecture 的縮寫,目前已經成爲了linux的主流音頻體系結構
在內核設備驅動層,ALSA提供了alsa-driver,同時在應用層,ALSA爲我們提供了alsa-lib,應用程序只要調用alsa-lib提供的API,即可以完成對底層音頻硬件的控制。


3.ALSA設備文件

ALSA驅動核心會創建和管理一些設備節點,比如:

/dev/snd/controlC0: 一個控制結點,(應用程序用它來控制聲卡,例如通道選擇,音量的控制等)

/dev/snd/pcmC0D0p:用於播放的pcm設備

/dev/snd/pcmC0D0c:用於錄音的pcm設備

C0D0代表的是聲卡0中的設備0,最後一個c代表capture,最後一個p代表playback。

4.聲卡的建立流程
第一步,創建snd_card的一個實例
snd_card可以說是整個ALSA音頻驅動最頂層的一個結構,整個聲卡的軟件邏輯結構開始於該結構,幾乎所有與聲音相關的邏輯設備都是在snd_card的管理之下.
第二步,創建聲卡的功能部件(邏輯設備),例如PCM, Mixer等,並將邏輯設備與步驟一創建的聲卡綁定
通常, alsa-driver的已經提供了一些常用的部件的創建函數,PCM ---- snd_pcm_new()、CONTROL -- snd_ctl_create()
第三步,將聲卡註冊進ALSA框架

經過以上的創建步驟之後,聲卡的邏輯結構如下圖所示:



5.PCM設備的創建

對於一個pcm設備,可以生成兩個設備文件,一個用於playback,一個用於capture,代碼中也確定了他們的命名規則:

  • playback -- pcmCxDxp,通常系統中只有一個聲卡和一個pcm,它就是pcmC0D0p
  • capture -- pcmCxDxc,通常系統中只有一個聲卡和一個pcm,它就是pcmC0D0c

新建一個pcm設備的過程:

  • snd_card_create ,pcm是聲卡下的一個設備(部件),所以第一步是要創建一個聲卡
  • snd_pcm_new, 調用該api創建一個pcm,在該api中會做以下事情:
建立playback stream,相應的substream也同時建立
建立capture stream,相應的substream也同時建立
調用snd_device_new()把該pcm掛到聲卡中,參數ops中的dev_register字段指向了函數snd_pcm_dev_register,這個回調函數會在聲卡的註冊階段被調用
  • snd_pcm_set_ops, 設置操作該pcm的控制/操作接口函數,參數中的snd_pcm_ops結構中的函數通常就是我們驅動要實現的函數
  • snd_card_register 註冊聲卡,在這個階段會遍歷聲卡下的所有邏輯設備,並且調用各設備的註冊回調函數,對於pcm,就是第二步提到的snd_pcm_dev_register函數,該回調函數建立了和用戶空間應用程序( alsa-lib)通信所用的設備文件節點:/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc
6.Control設備的創建

Control設備和PCM設備一樣,都屬於聲卡下的邏輯設備。用戶空間的應用程序通過alsa-lib訪問該Control設備,讀取或設置control的狀態,從而達到控制音頻Codec進行各種Mixer等控制操作。

要自定義一個Control,我們首先要定義3各回調函數:info,get和put。然後,定義一個snd_kcontrol_new結構:

static struct snd_kcontrol_new my_control __devinitdata = {  
    .iface = SNDRV_CTL_ELEM_IFACE_MIXER,  
    .name = "PCM Playback Switch",  
    .index = 0,  
    .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,  
    .private_value = 0xffff,  
    .info = my_control_info,  
    .get = my_control_get,  
    .put = my_control_put  
}; 
iface字段指出了control的類型
name字段是該control的名字
index字段用於保存該control的在該卡中的編號。
access字段包含了該control的訪問類型。
private_value字段包含了一個任意的長整數類型值。

info回調函數用於獲取control的詳細信息
get回調函數用於讀取control的當前值,並返回給用戶空間的應用程序。
put回調函數用於把應用程序的控制值設置到control中。

我們需要在我們的驅動程序初始化時主動調用snd_pcm_new()函數創建pcm設備,而control設備則在snd_card_create()內被創建,snd_card_create()通過調用snd_ctl_create()函數創建control設備節點。所以我們無需顯式地創建control設備,只要建立聲卡,control設備被自動地創建。

7.移動設備中的ALSA(ASoC)

ASoC--ALSA System on Chip ,是爲了更好地支持嵌入式處理器和移動設備中的音頻Codec的一套軟件體系。ASoC不能單獨存在,它建立在標準ALSA驅動之上,必須和標準的ALSA驅動框架相結合才能工作。

在軟件層面, ASoC也把嵌入式設備的音頻系統同樣分爲3大部分, Machine, Platform和Codec

Machine驅動:跟單板相關,綁定Platform和Codec驅動,即表明使用的是哪個Platform,哪個CPU DAI、DMA、Codec和Codec DAI。

Platform驅動 :它包含了該SoC平臺的音頻DMA和音頻接口DAI的配置和控制( I2S, PCM等等)

Codec驅動 :它包含了一些音頻的控件( Controls),音頻接口, DAMP(動態音頻電源管理)的定義和某些Codec IO功能。所有的Codec驅動都要提供以下特性:

  • Codec DAI 和 PCM的配置信息;
  • Codec的IO控制方式( I2C, SPI等);
  • Mixer和其他的音頻控件;
  • Codec的ALSA音頻操作接口;

8.ASoC架構中的Machine

ASoC把聲卡註冊爲Platform Device。
Machine驅動在一個重要的數據結構snd_soc_dai_link中,指定了Platform、 Codec、 codec_dai、 cpu_dai的名字,稍後Machine驅動將會利用這些名字去匹配已經在系統中註冊的platform, codec, dai,這些註冊的部件都是在另外相應的Platform驅動和Codec驅動的代碼文件中定義的,這樣看來, Machine驅動的設備初始化代碼無非就是選擇合適Platform和Codec以及dai,用他們填充以上幾個數據結構,然後註冊Platform設備即可。

platform總線會匹配這兩個名字相同的device和driver,同時會觸發soc_probe的調用,它正是整個ASoC驅動初始化的入口。

soc_probe函數中會完成以下任務:

  • 調用標準的alsa函數創建聲卡實例
  • 挨個調用了codec, dai和platform驅動的probe函數
  • 調用了soc_new_pcm()函數用於創建標準alsa驅動的pcm邏輯設備
  • 最後則是調用標準alsa驅動的聲卡註冊函數對聲卡進行註冊

9.ASoC架構中的Codec

在移動設備中, Codec的作用可以歸結爲4種,分別是:

  • 對PCM等信號進行D/A轉換,把數字的音頻信號轉換爲模擬信號
  • 對Mic、 Linein或者其他輸入源的模擬信號進行A/D轉換,把模擬的聲音信號轉變CPU能夠處理的數字信號
  • 對音頻通路進行控制,比如播放音樂,收聽調頻收音機,又或者接聽電話時,音頻信號在codec內的流通路線是不一樣的
  • 對音頻信號做出相應的處理,例如音量控制,功率放大, EQ控制等等

描述Codec的最主要的幾個數據結構分別是: 

snd_soc_codec, snd_soc_codec_driver, snd_soc_dai, snd_soc_dai_driver,其中的snd_soc_dai和snd_soc_dai_driver在ASoC的Platform驅動中也會使用到, Platform和Codec的DAI通過snd_soc_dai_link結構,在Machine驅動中進行綁定連接。

Codec驅動的步驟:

  • 定義snd_soc_codec_driver和snd_soc_dai_driver的實例,然後調用snd_soc_register_codec函數對Codec進行註冊。
在snd_soc_register_codec函數中,它申請了一個snd_soc_codec結構的實例:確定codec的名字,這個名字很重要, Machine驅動定義的snd_soc_dai_link中會指定每個link的codec和dai的名字,進行匹配綁定時就是通過和這裏的名字比較,從而找到該Codec的!然後初始化snd_soc_codec結構的各個字段,多數字段的值來自上面定義的snd_soc_codec_driver的實例。

  • 通過snd_soc_register_dais函數對本Codec的dai進行註冊
在snd_soc_register_dais函數中爲每個snd_soc_dai實例分配內存,確定dai的名字,用snd_soc_dai_driver實例的字段對它進行必要初始化
  • 最後,它把codec實例鏈接到全局鏈表codec_list中,把dai鏈接到全局鏈表dai_list中,並且調snd_soc_instantiate_cards函數觸發Machine驅動進行一次匹配綁定操作
10.ASoC架構中的Platform
Platform驅動的主要作用是完成音頻數據的管理,最終通過CPU的數字音頻接口( DAI)把音頻數據傳送給Codec進行處理,最終由Codec輸出驅動耳機或者是喇叭的音信信號。在具體實現上, ASoC有把Platform驅動分爲兩個部分: snd_soc_platform_driver和snd_soc_dai_driver。其中, platform_driver負責管理音頻數據,把音頻數據通過dma或其他操作傳送至cpu dai中, dai_driver則主要完成cpu一側的dai的參數配置,同時也會通過一定的途徑把必要的dma等參數與snd_soc_platform_driver進行交互。

snd_soc_platform_driver的註冊
實現該驅動大致可以分爲以下幾個步驟:
  • 定義一個snd_soc_platform_driver結構的實例;
  • 在platform_driver的probe回調中利用ASoC的API: snd_soc_register_platform()註冊上面定義的實例;
  • 實現snd_soc_platform_driver中的各個回調函數

snd_soc_platform_driver中的ops字段是一個snd_pcm_ops結構,實現該結構中的各個回調函數是soc platform驅動的主要工作,他們基本都涉及dma操作以及dma buffer的管理等工作。下面介紹幾個重要的回調函數:
  • ops.open:當應用程序打開一個pcm設備時,該函數會被調用
  • ops.hw_params:驅動的hw_params階段,該函數會被調用,該函數會通過snd_soc_dai_get_dma_data函數獲得對應的dai的dma參數
  • ops.prepare:正式開始數據傳送之前會調用該函數,該函數通常會完成dma操作的必要準備工作。
  • ops.trigger:數據傳送的開始,暫停,恢復和停止時,該函數會被調用。
  • ops.pointer:該函數返回傳送數據的當前位置
cpu的snd_soc_dai driver驅動的註冊
dai驅動通常對應cpu的一個或幾個I2S/PCM接口,與snd_soc_platform一樣, dai驅動也是實現爲一個platform driver,實現一個dai驅動大致可以分爲以下幾個步驟:
  • 定義一個snd_soc_dai_driver結構的實例;
  • 在對應的platform_driver中的probe回調中通過API: snd_soc_register_dai或者snd_soc_register_dais,注
  • 冊snd_soc_dai實例;
  • 實現snd_soc_dai_driver結構中的probe、 suspend等回調;
  • 實現snd_soc_dai_driver結構中的snd_soc_dai_ops字段中的回調函數
它的ops字段指向一個snd_soc_dai_ops結構,該結構實際上是一組回調函數的集合, dai的配置和控制幾乎都是通過這些回調函數來實現的,這些回調函數基本可以分爲3大類,驅動程序可以根據實際情況實現其中的一部分:
  • 工作時鐘配置函數 通常由machine驅動調用
  • 標準的snd_soc_ops回調 通常由soc-core在進行PCM操作時調用
  • 抗pop, pop聲 由soc-core調用
11.音頻數據的dma操作
soc-platform驅動的最主要功能就是要完成音頻數據的傳送,大多數情況下,音頻數據都是通過dma來完成的

申請dma buffer

在聲卡的建立階段,pcm_new回調函數會被調用,函數進一步爲playback和capture分別調用preallocate_dma_buffer函數分配dma內存,然後完成substream->dma_buffer的初始化賦值工作

在聲卡的hw_params階段, snd_soc_platform_driver結構的ops->hw_params會被調用,在該回調用,通常會使用api: snd_pcm_set_runtime_buffer()把substream->dma_buffer的數值拷貝到substream->runtime的相關字段中( .dma_area, .dma_addr, .dma_bytes),這樣以後就可以通過substream->runtime獲得這些地址和大小信息了。

dma buffer獲得後,即是獲得了dma操作的源地址,那麼目的地址在哪裏?其實目的地址當然是在dai中,也就是前面介紹的snd_soc_dai結構的playback_dma_data和capture_dma_data字段中,而這兩個字段的值也是在hw_params階段,由snd_soc_dai_driver結構的ops->hw_params回調,利用api: snd_soc_dai_set_dma_data進行設置的。緊隨其後, snd_soc_platform_driver結構的ops->hw_params回調利用api: snd_soc_dai_get_dma_data獲得這些dai的dma信息,其中就包括了dma的目的地址信息。這些dma信息通常還會被保存在substream->runtime->private_data中,以便在substream的整個生命週期中可以隨時獲得這些信息,從而完成對dma的配置和操作

dma buffer管理

播放時,應用程序把音頻數據源源不斷地寫入dma buffer中,然後相應platform的dma操作則不停地從該buffer中取
出數據,經dai送往codec中。錄音時則正好相反, codec源源不斷地把A/D轉換好的音頻數據經過dai送入dmabuffer中,而應用程序則不斷地從該buffer中讀走音頻數據。


以上只是參考別人博客,概括性的總結了一下Linux音頻子系統,如果想深入瞭解,可以查看博客:
http://blog.csdn.net/droidphone/article/details/6289712


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章