ALSA(Advanced Linux Sound Architecture)是Linux内核默认的音频驱动框架。
OSS(Open Sound System)是一个早期的Unix/Linux平台上的音频接口。目前已经被ALSA所取代。
OSS有声卡独占问题,而ALSA支持混音,能在不同应用程序之间声卡共享。
在ALSA/OSS之上还有一些API接口标准:
OpenAL官网:
http://www.openal.org/
参考:
http://www.alsa-project.org/main/index.php/Tutorials_and_Presentations
包含官方提供的若干教程。
http://blog.sina.com.cn/s/blog_5707eebf0100tjbe.html
这篇文章介绍了ALSA的代码文件结构。
http://blog.csdn.net/droidphone/article/details/6271122
系列文章共8篇,详细介绍了ALSA和ASOC。该博客还有7篇与DAPM有关的博文。
http://blog.chinaunix.net/uid-20602659-id-2981336.html
系列文章共2篇。主要偏重于ASOC驱动的具体实现,而不是背后的机制原理。
https://www.alsa-project.org/alsa-doc/alsa-lib/
C library reference
http://equalarea.com/paul/alsa-audio.html
A Tutorial on Using the ALSA Audio API
首先使用lspci -v
列出所有的硬件设备,其中当然也包括了音频设备。例如我的PC上是这样的:
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项目提供的工具软件集,以下是一些测试样例:
播放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
列出音频输出设备
aplay -l
列出音频输入设备
arecord -l
这是专为嵌入式设备设计的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。这部分用于前两部分之间的协调,起到粘合剂的作用。它通常是与具体使用的机器相关的。
参考:
https://processors.wiki.ti.com/index.php/Sitara_Linux_Audio_Driver_Overview
Sitara Linux Audio Driver Overview
以三星设备为例,Platform driver和Machine driver在sound/soc/samsung下,该文件夹的Makefile节选如下:
# 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节选如下:
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格式的数据(有时也被称作原始音频数据),然后才能发送给相关的硬件。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。
DAI: Digital Audio Interface
DAPM:Dynamic Audio Power Management
网上的文章有不少都是针对2.6.X的老内核的,现将3.X引入的变化,罗列如下:(自己摘录,仅供备忘,非官方内容)
1.snd_card_create改为snd_card_new。
http://www.rosoo.net/a/201203/15847.html
ALSA SOC在Linux3.1上的一些改进
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
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名称相同,参数却不同。
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即可。
音量操作虽然在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设置音量的代码片段如下(注意,该片段没有异常处理):
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结构中。后面都是针对这个结构的写操作。
您的打赏,是对我的鼓励