http://antkillerfarm.github.io/
參考文獻
ALSA(Advanced Linux Sound Architecture)是Linux內核默認的音頻驅動框架。以下是一些參考資料:
http://www.alsa-project.org/main/index.php/Tutorials_and_Presentations
包含官方提供的若干教程。
http://blog.sina.com.cn/s/blog_5707eebf0100tjbe.html
這篇文章介紹了ALSA的代碼文件結構。
/content/1021331.html
系列文章共8篇,詳細介紹了ALSA和ASOC。該博客還有7篇與DAPM有關的博文。
http://blog.chinaunix.net/uid-20602659-id-2981336.html
系列文章共2篇。主要偏重於ASOC驅動的具體實現,而不是背後的機制原理。
關於ALSA的應用層
查找PC本機驅動
首先使用
lspci -v
列出所有的硬件設備,其中當然也包括了音頻設備。例如我的PC上是這樣的:
[code]Audio device: Intel Corporation 6 Series/C200 Series Chipset Family High Definition Audio Controller (rev 05)
Subsystem: ASUSTeK Computer Inc. Device 1b73
Flags: bus master, fast devsel, latency 0, IRQ 54
Memory at df000000 (64-bit, non-prefetchable) [size=16K]
Capabilities: <access denied>
Kernel driver in use: snd_hda_intel
從驅動名
snd_hda_intel
可以很輕松的發現,這個驅動的源代碼是sound/pci/hda/hda_intel.c。
在/dev/snd下,可以看到聲卡的子設備。
對於ASoC驅動來說,因為它是platform驅動,所以可以在/sys/bus/platform下,找到驅動的相關痕跡。
alsa-utils
alsa-utils是ALSA項目提供的工具軟件集,以下是一些測試樣例:
播放wave文件
aplay /mnt/nfs/test.wav
變頻播放(以44 KHz來播放音頻)
aplay -D rate_44k /mnt/nfs/test.wav
錄音,以20秒的間隔(-d 20),立體聲(-c 2),頻率是 8000Hz來錄制Wave格式音頻
arecord -d 20 -c 2 -t wav -r 8000 -f "Signed 16 bit Little Endian" /mnt/nfs/test.wav
測試混音播放(先是播放test1.wav,然後再同時播放test2.wav)
aplay -D plug:dmix_44k /mnt/nfs/test1.wav &
aplay -D plug:dmix_44k /mnt/nfs/test2.wav
設置放音增益(0 to 3)
amixer set Master 1
設置錄音音量(0-31)
amixer set Line 10
列出可用的ALSA設備
aplay -L
ALSA System on Chip (ASoC) layer
這是專為嵌入式設備設計的ALSA框架。官網是:
http://www.alsa-project.org/main/index.php/ASoC
上圖是TI某平台的ASOC架構圖。其中的ASoC layer可分為三個不同的部分:
1.Codec driver。這部分是平台無關的,包括了音頻接口配置、音頻控制、電源管理和其他IO功能。所謂平台是若干使用相同SOC的機型的集合。
2.Platform driver。這部分主要是平台相關的音頻接口驅動和相關的DMA驅動。
3.Machine driver。這部分用於前兩部分之間的協調,起到粘合劑的作用。它通常是與具體使用的機器相關的。
以三星設備為例,Platform driver和Machine driver在sound/soc/samsung下,該文件夾的Makefile節選如下:
[code]# S3c24XX Platform Support
snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o
snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o
obj-$(CONFIG_SND_S3C24XX_I2S) += snd-soc-s3c24xx-i2s.o
obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o
# S3C24XX Machine Support
snd-soc-s3c24xx-uda134x-objs := s3c24xx_uda134x.o
snd-soc-s3c24xx-simtec-objs := s3c24xx_simtec.o
obj-$(CONFIG_SND_SOC_SAMSUNG_S3C24XX_UDA134X) += snd-soc-s3c24xx-uda134x.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC) += snd-soc-s3c24xx-simtec.o
Codec driver在sound/soc/codecs下,該文件夾的Makefile節選如下:
[code]snd-soc-uda134x-objs := uda134x.o
snd-soc-uda1380-objs := uda1380.o
obj-$(CONFIG_SND_SOC_UDA134X) += snd-soc-uda134x.o
obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o
snd_soc_register_codec函數用來注冊Codec driver。
snd_soc_register_component函數用來注冊外設接口驅動(例如I2S)。
snd_soc_register_platform函數用來注冊Platform driver。
Machine driver通過snd_soc_dai_link結構的codec_name和platform_name成員,指定匹配的Codec driver和Platform driver。
三部分的加載順序:
1.驅動對象的加載,一般在模塊加載時進行。各對象的先後順序無關緊要。
2.設備對象的加載,一般在驅動對象加載之後,而在ALSA加載之前。由於模塊加載的初始化優先級是device_initcall,而alsa_sound_last_init函數的初始化優先級是late_initcall_sync,因此設備對象的初始化優先級設為late_initcall是比較妥當的。
3.由於Machine driver在Codec driver和Platform driver之間起著橋梁作用,因此需要在加載了Codec和Platform的設備對象之後,再進行Machine設備對象的加載。
PCM, I2S, DMA
無論何種形式的音頻,最終都要轉換成PCM格式的數據(有時也被稱作原始音頻數據),然後才能發送給相關的硬件。CPU和音頻芯片之間的數據接口,在PC上主要是AC97接口,而在嵌入式設備中,則多為I2S接口。
通常使用DMA方式傳輸原始音頻數據。在3.X內核中,一般將PCM的DMA功能設置為Platform driver,而將I2S功能設置為該driver的一個component。
從DMA的實現方式來看,主要分兩種:
1.Platform driver包含i2s和pcm兩個文件。它的核心是設置snd_pcm_ops數據結構。例如pxa2xx-i2s.c和pxa2xx-pcm.c。
2.Platform driver只包含i2s一個文件。它主要調用與dmaengine_pcm_platform結構相關的函數。例如s3c24xx-i2s.c。
ASoC術語表
DAI: Digital Audio Interface
DAPM:Dynamic Audio Power Management
ALSA從2.6.X到3.X的變化細節
網上的文章有不少都是針對2.6.X的老內核的,現將3.X引入的變化,羅列如下:(自己摘錄,僅供備忘,非官方內容)
1.snd_card_create改為snd_card_new。
從Gstreamer到ALSA
0.概述
Gstreamer通過alsasink和alsasrc這兩個插件訪問ALSA API。這兩個插件在gst-plugins-base代碼的ext/alsa文件夾下。
gst-plugins-base的git地址是:
git://anongit.freedesktop.org/gstreamer/gst-plugins-base
ALSA API的代碼在alsa-lib中,它的git地址是:
git://git.alsa-project.org/alsa-lib.git
ALSA API會調用ALSA driver,而driver的代碼肯定在linux內核中。
alsa-lib中的很多常用功能,被做成了動態鏈接庫,例如:alsa-lib/libasound_module_pcm_pulse.so,這些鏈接庫的代碼在alsa-plugins中。alsa-plugins的git地址是:
git://git.alsa-project.org/alsa-plugins.git
aplay等ALSA工具的代碼在alsa-utils中,其地址如下:
git://git.alsa-project.org/alsa-utils.git
1.open操作
gst-plugins-base/ext/alsa/gstalsasink.c: gst_alsasink_open
這一步之後,進入alsa-lib作用域。
alsa-lib/src/pcm/pcm.c: snd_pcm_open
alsa-lib/src/pcm/pcm.c: snd_pcm_open_noupdate
alsa-lib/src/pcm/pcm.c: snd_pcm_open_conf
alsa-lib中pcm有很多插件,提供諸如硬件、復制、線性變換等操作。詳見alsa-lib/include/pcm_plugin.h。
alsa-lib/src/pcm/pcm_hw.c: _snd_pcm_hw_open
alsa-lib/src/pcm/pcm_hw.c: snd_pcm_hw_open
alsa-lib/include/local.h: snd_open_device
這個函數調用open系統調用,進入內核空間。在內核空間中根據driver模型的不同,進入linux-kernel/sound/core/pcm_native.c: snd_pcm_open(ALSA)或linux-kernel/sound/soc/soc-pcm.c: soc_pcm_open(ASOC),注意這裡的snd_pcm_open和alsa-lib中的snd_pcm_open名稱相同,參數卻不同。
2.write操作
gst-plugins-base/ext/alsa/gstalsasink.c: gst_alsasink_write
這一步之後,進入alsa-lib作用域。
alsa-lib/src/pcm/pcm.c: snd_pcm_writei
alsa-lib有兩種寫操作:snd_pcm_writei和snd_pcm_writen。snd_pcm_writei表示寫入交織的PCM數據,而snd_pcm_writen表示寫入非交織的PCM數據。
alsa-lib/src/pcm/pcm_local.h: _snd_pcm_writei
alsa-lib/src/pcm/pcm_hw.c: snd_pcm_hw_writei
這個函數調用ioctl系統調用,參數為SNDRV_PCM_IOCTL_WRITEI_FRAMES,pcm數據的buffer,放在snd_xferi結構中,傳入內核空間。
linux-kernel/sound/core/pcm_native.c: snd_pcm_playback_ioctl1
linux-kernel/sound/core/pcm_lib.c: snd_pcm_lib_write
linux-kernel/sound/core/pcm_lib.c: snd_pcm_lib_write_transfer
這裡會有兩個分支,以下僅討論DMA方式的處理。pcm數據放在snd_pcm_substream.runtime->dma_area指向的緩沖區。open的時候,使用mmap做好snd_pcm_substream.runtime->dma_area和snd_pcm_substream.runtime->dma_addr之間的映射。底層驅動只須操作dma_addr即可。
3.音量操作
音量操作雖然在Gstreamer和ALSA中都有,但彼此並無調用關系。Gstreamer中的音量調整是用軟件改變PCM數據實現的,可稱為軟音量。與之相對的是音頻硬件功放經揚聲器所產生的音量,是為硬音量。ALSA的音量可以是軟音量,也可以是硬音量。
Gstreamer的音量操作步驟:
gst-plugins-base/gst/playback/gstplaysink.c: gst_play_sink_set_volume
這個函數主要是通過設置playsink的volume屬性來實現操作。
Gstreamer軟音量的實現代碼在gst-plugins-base/gst/volume/gstvolume.c中。
除此之外,還有流媒體音量設置gst_stream_volume_set_volume。詳見gst-plugins-base/gst-libs/gst/audio/streamvolume.c。
ALSA設置音量的代碼片段如下(注意,該片段沒有異常處理):
[code]void SetAlsaMasterVolume(long volume)
{
long min, max;
snd_mixer_t *handle;
snd_mixer_selem_id_t *sid;
const char *card = "default";
const char *selem_name = "Master";
snd_mixer_open(&handle, 0);
snd_mixer_attach(handle, card);
snd_mixer_selem_register(handle, NULL, NULL);
snd_mixer_load(handle);
snd_mixer_selem_id_alloca(&sid);
snd_mixer_selem_id_set_index(sid, 0);
snd_mixer_selem_id_set_name(sid, selem_name);
snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid);
snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
snd_mixer_selem_set_playback_volume_all(elem, volume * max / 100);
snd_mixer_close(handle);
}
ALSA的音量操作步驟:(只討論硬音量)
alsa-lib/src/mixer/simple.c: snd_mixer_selem_set_playback_volume
alsa-lib/src/mixer/simple_none.c: _snd_mixer_selem_set_volume
這一步將volume數據放到selem_none_t.str.vol結構中。後面都是針對這個結構的寫操作。
alsa-lib/src/mixer/simple_none.c: selem_write
alsa-lib/src/mixer/simple_none.c: selem_write_main
alsa-lib/src/mixer/simple_none.c: elem_write_volume
alsa-lib/src/control/hcontrol.c: snd_hctl_elem_write
alsa-lib/src/control/control.c: snd_ctl_elem_write
alsa-lib/src/control/control_hw.c: snd_ctl_hw_elem_write
這個函數調用ioctl系統調用,參數為SNDRV_CTL_IOCTL_ELEM_WRITE,進入內核空間。
linux-kernel/sound/core/control.c: snd_ctl_elem_write_user
linux-kernel/sound/core/control.c: snd_ctl_elem_write
驅動上層的數據放在snd_card.controls鏈表中。這個鏈表中的元素是snd_kcontrol類型的。而驅動底層使用snd_kcontrol_new類型的數據。
在設備初始化時,使用snd_soc_cnew或snd_ctl_new1函數從snd_kcontrol_new類型的模板中,生成相應的snd_kcontrol類型的數據。snd_kcontrol_new類型的數據可以用SOC_SINGLE、SOC_DOUBLE_VALUE之類的宏生成,其默認的put函數是snd_soc_put_volsw。
linux-kernel/sound/soc/soc-ops.c: snd_soc_put_volsw
linux-kernel/sound/soc/soc-io.c: snd_soc_component_update_bits
linux-kernel/drivers/base/regmap/regmap.c: regmap_update_bits_check
使用regmap技術映射I2C、SPI接口地址之後,即可調用相關函數修改相關音頻外設的寄存器值了。