Antkillerfarm Hacking V7.0

ALSA(一)

2015-11-13

参考文献

ALSA(Advanced Linux Sound Architecture)是Linux内核默认的音频驱动框架。

OSS(Open Sound System)是一个早期的Unix/Linux平台上的音频接口。目前已经被ALSA所取代。

OSS有声卡独占问题,而ALSA支持混音,能在不同应用程序之间声卡共享。

在ALSA/OSS之上还有一些API接口标准:

  • PulseAudio
  • SDL
  • OpenAL

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

关于ALSA的应用层

查找PC本机驱动

首先使用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-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 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。这部分用于前两部分之间的协调,起到粘合剂的作用。它通常是与具体使用的机器相关的。

参考:

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, 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。

http://www.rosoo.net/a/201203/15847.html

ALSA SOC在Linux3.1上的一些改进

从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设置音量的代码片段如下(注意,该片段没有异常处理):

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结构中。后面都是针对这个结构的写操作。

Fork me on GitHub